diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c0d42ab5716c061a8c5307eac4a75252d0402ab8
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -0,0 +1,87 @@
+name: Bug
+description: File a bug report
+title: "[bug]: "
+labels: ["bug", "triage"]
+#assignees:
+#  - octocat
+body:
+  - type: markdown
+    attributes:
+      value: |
+        Thanks for creating a report! The issue has entered the triage process. That means the issue will wait in this status until a Bug Marshal has an opportunity to review the issue. Once the issue has been reviewed you will receive comments regarding the next steps towards resolution. Please note that log messages and other files should not be sent to the Sangoma Asterisk Team unless explicitly asked for. All files should be placed on this issue in a sanitized fashion as needed.
+
+        A good first step is for you to review the Asterisk Issue Guidelines if you haven't already. The guidelines detail what is expected from an Asterisk issue report.
+
+        Then, if you are submitting a patch, please review the Patch Contribution Process.
+
+        Please note that once your issue enters an open state it has been accepted. As Asterisk is an open source project there is no guarantee or timeframe on when your issue will be looked into. If you need expedient resolution you will need to find and pay a suitable developer. Asking for an update on your issue will not yield any progress on it and will not result in a response. All updates are posted to the issue when they occur.
+
+        Please note that by submitting data, code, or documentation to Sangoma through GitHub, you accept the Terms of Use present at
+        https://www.asterisk.org/terms-of-use/.      
+        Thanks for taking the time to fill out this bug report!
+  - type: dropdown
+    id: severity
+    attributes:
+      label: Severity
+      options:
+        - Trivial
+        - Minor
+        - Major
+        - Critical
+        - Blocker
+    validations:
+      required: true
+  - type: input
+    id: versions
+    attributes:
+      label: Versions
+      description: Enter one or more versions separated by commas.
+    validations:
+      required: true
+  - type: input
+    id: components
+    attributes:
+      label: Components/Modules
+      description: Enter one or more components or modules separated by commas.
+    validations:
+      required: true
+  - type: textarea
+    id: environment
+    attributes:
+      label: Operating Environment
+      description: OS,  Disribution, Version, etc.
+    validations:
+      required: true
+  - type: dropdown
+    id: frequency
+    attributes:
+      label: Frequency of Occurrence
+      options:
+        - "Never"
+        - "One Time"
+        - "Occasional"
+        - "Frequent"
+        - "Constant"
+  - type: textarea
+    id: description
+    attributes:
+      label: Issue Description
+    validations:
+      required: true
+  - type: textarea
+    id: logs
+    attributes:
+      label: Relevant log output
+      description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+      render: shell
+  - type: markdown
+    attributes:
+      value: |
+        [Asterisk Issue Guidelines](https://wiki.asterisk.org/wiki/display/AST/Asterisk+Issue+Guidelines)
+  - type: checkboxes
+    id: guidelines
+    attributes:
+      label: Asterisk Issue Guidelines
+      options:
+        - label: Yes, I have read the Asterisk Issue Guidelines
+          required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ce54a75bf43544c417ba35cabd38b8900ee05c76
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+  - name: Asterisk Community Support
+    url: https://community.asterisk.org
+    about: Please ask and answer questions here.
diff --git a/.github/ISSUE_TEMPLATE/improvement.yml b/.github/ISSUE_TEMPLATE/improvement.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9f8f73b8cefbfd1b39f66a125cdb520a093ba72e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/improvement.yml
@@ -0,0 +1,27 @@
+name: Improvement
+description: Request an improvement to existing functionality
+title: "[improvement]: "
+labels: ["improvement", "triage"]
+body:
+  - type: markdown
+    attributes:
+      value: |
+        Thanks for creating a report! The issue has entered the triage process. That means the issue will wait in this status until a Bug Marshal has an opportunity to review the issue. Once the issue has been reviewed you will receive comments regarding the next steps towards resolution. Please note that log messages and other files should not be sent to the Sangoma Asterisk Team unless explicitly asked for. All files should be placed on this issue in a sanitized fashion as needed.
+
+        A good first step is for you to review the Asterisk Issue Guidelines if you haven't already. The guidelines detail what is expected from an Asterisk issue report.
+
+        Then, if you are submitting a patch, please review the Patch Contribution Process.
+
+        Please note that once your issue enters an open state it has been accepted. As Asterisk is an open source project there is no guarantee or timeframe on when your issue will be looked into. If you need expedient resolution you will need to find and pay a suitable developer. Asking for an update on your issue will not yield any progress on it and will not result in a response. All updates are posted to the issue when they occur.
+
+        Please note that by submitting data, code, or documentation to Sangoma through GitHub, you accept the Terms of Use present at
+        https://www.asterisk.org/terms-of-use/.      
+        Thanks for taking the time to fill out this bug report!
+  - type: textarea
+    id: description
+    attributes:
+      label: Improvement Description
+      description: Describe the improvement requested in as much detail as possible
+    validations:
+      required: true
+      
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/new-feature.yml b/.github/ISSUE_TEMPLATE/new-feature.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d82c501609853f26910120d616421203bf549bc3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/new-feature.yml
@@ -0,0 +1,27 @@
+name: New Feature
+description: Request a New Feature
+title: "[new-feature]: "
+labels: ["new-feature", "triage"]
+body:
+  - type: markdown
+    attributes:
+      value: |
+        Thanks for creating a report! The issue has entered the triage process. That means the issue will wait in this status until a Bug Marshal has an opportunity to review the issue. Once the issue has been reviewed you will receive comments regarding the next steps towards resolution. Please note that log messages and other files should not be sent to the Sangoma Asterisk Team unless explicitly asked for. All files should be placed on this issue in a sanitized fashion as needed.
+
+        A good first step is for you to review the Asterisk Issue Guidelines if you haven't already. The guidelines detail what is expected from an Asterisk issue report.
+
+        Then, if you are submitting a patch, please review the Patch Contribution Process.
+
+        Please note that once your issue enters an open state it has been accepted. As Asterisk is an open source project there is no guarantee or timeframe on when your issue will be looked into. If you need expedient resolution you will need to find and pay a suitable developer. Asking for an update on your issue will not yield any progress on it and will not result in a response. All updates are posted to the issue when they occur.
+
+        Please note that by submitting data, code, or documentation to Sangoma through GitHub, you accept the Terms of Use present at
+        https://www.asterisk.org/terms-of-use/.      
+        Thanks for taking the time to fill out this bug report!
+  - type: textarea
+    id: description
+    attributes:
+      label: Feature Description
+      description: Describe the new feature requested in as much detail as possible
+    validations:
+      required: true
+      
\ No newline at end of file
diff --git a/.github/workflows/AsteriskReleaser.yml b/.github/workflows/AsteriskReleaser.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9a9b46a833b58787eb55290a8a1285112ca75cb6
--- /dev/null
+++ b/.github/workflows/AsteriskReleaser.yml
@@ -0,0 +1,61 @@
+# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
+name: Asterisk Release
+run-name: ${{ github.actor }} is creating an Asterisk release
+on:
+  workflow_dispatch:
+    inputs:
+      is_security:
+        description: 'Security or Hotfix?'
+        required: true
+        type: boolean
+        default: false
+      new_version:
+        description: |
+          New Version
+          Examples:
+            21.4.0-rc1, 21.4.0-rc2, 21.4.0, 21.4.1
+            certified/21.4-cert1-rc1, certified/21.4-cert1
+        required: true
+        type: string
+      start_version:
+        description: |
+          Last Version
+          Only use when you KNOW that the automated
+          process won't get it right.'
+        required: false
+        type: string
+      push_live:
+        description: 'Push live?'
+        required: true
+        type: boolean
+        default: false
+      send_email:
+        description: 'Send announcement emails?'
+        required: true
+        type: boolean
+        default: false
+
+jobs:
+  ReleaseAsterisk:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Run Releaser
+        uses: asterisk/asterisk-ci-actions/AsteriskReleaserComposite@main
+        with:
+          is_security: ${{inputs.is_security}}
+          new_version: ${{inputs.new_version}}
+          start_version: ${{inputs.start_version}}
+          push_live: ${{inputs.push_live}}
+          send_email: ${{inputs.send_email}}
+          repo: ${{github.repository}}
+          asterisk_mail_list_ga: ${{vars.ASTERISK_MAIL_LIST_GA}}
+          asterisk_mail_list_rc: ${{vars.ASTERISK_MAIL_LIST_RC}}
+          asterisk_mail_list_cert_ga: ${{vars.ASTERISK_MAIL_LIST_CERT_GA}}
+          asterisk_mail_list_cert_rc: ${{vars.ASTERISK_MAIL_LIST_CERT_RC}}
+          asterisk_mail_list_sec: ${{vars.ASTERISK_MAIL_LIST_CERT_SEC}}
+          gpg_private_key: ${{secrets.ASTDEV_GPG_PRIV_KEY}}
+          github_token: ${{secrets.GITHUB_TOKEN}}
+          application_id: ${{secrets.ASTERISK_ORG_ACCESS_APP_ID}}
+          application_private_key: ${{secrets.ASTERISK_ORG_ACCESS_APP_PRIV_KEY}}
+          asteriskteamsa_username: ${{secrets.ASTERISKTEAMSA_GMAIL_ACCT}}
+          asteriskteamsa_token: ${{secrets.ASTERISKTEAMSA_GMAIL_TOKEN}}
diff --git a/.github/workflows/CherryPickTest.yml b/.github/workflows/CherryPickTest.yml
new file mode 100644
index 0000000000000000000000000000000000000000..88c0bc4029fc71a8624165028fdb8713dfc44604
--- /dev/null
+++ b/.github/workflows/CherryPickTest.yml
@@ -0,0 +1,172 @@
+name: CherryPickTest
+run-name: "Cherry-Pick Tests for PR ${{github.event.number}}"
+on:
+  pull_request_target:
+    types: [ labeled ]
+
+concurrency:
+  group: ${{github.workflow}}-${{github.event.number}}
+  cancel-in-progress: true
+
+env:
+  PR_NUMBER:          ${{ github.event.number }}
+  MODULES_BLACKLIST:  ${{ vars.GATETEST_MODULES_BLACKLIST }} ${{ vars.UNITTEST_MODULES_BLACKLIST }}
+
+jobs:
+  IdentifyBranches:
+    name: IdentifyBranches
+    if: ${{ github.event.label.name == vars.CHERRY_PICK_TEST_LABEL }}
+    outputs:
+      branches:     ${{ steps.getbranches.outputs.branches }}
+      branch_count: ${{ steps.getbranches.outputs.branch_count }}
+    runs-on: ubuntu-latest
+    steps:
+      - name: DumpEnvironment
+        uses: asterisk/asterisk-ci-actions/DumpEnvironmentAction@main
+        with:
+          action-vars: ${{toJSON(vars)}}
+
+      - name: Remove Trigger Label, Add InProgress Label
+        env:
+          GH_TOKEN:          ${{ secrets.GITHUB_TOKEN }}
+        run: | 
+          gh pr edit --repo ${{github.repository}} \
+            --remove-label ${{vars.CHERRY_PICK_TEST_LABEL}} \
+            --remove-label ${{vars.CHERRY_PICK_CHECKS_PASSED_LABEL}} \
+            --remove-label ${{vars.CHERRY_PICK_CHECKS_FAILED_LABEL}} \
+            --remove-label ${{vars.CHERRY_PICK_GATES_PASSED_LABEL}} \
+            --remove-label ${{vars.CHERRY_PICK_GATES_FAILED_LABEL}} \
+            --remove-label ${{vars.CHERRY_PICK_TESTING_IN_PROGRESS}} \
+            ${{env.PR_NUMBER}} || :
+
+      - name: Get cherry-pick branches
+        uses: asterisk/asterisk-ci-actions/GetCherryPickBranchesFromPR@main
+        id: getbranches
+        with:
+          repo:                    ${{github.repository}}
+          pr_number:               ${{env.PR_NUMBER}}
+          cherry_pick_regex:       ${{vars.CHERRY_PICK_REGEX}}
+          github_token:            ${{secrets.GITHUB_TOKEN}}
+
+      - name: Check Branch Count
+        if: ${{ steps.getbranches.outputs.branch_count > 0 }}
+        env:
+          GH_TOKEN:          ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          gh pr edit --repo ${{github.repository}} \
+          --add-label ${{vars.CHERRY_PICK_TESTING_IN_PROGRESS}} \
+          ${{env.PR_NUMBER}} || :
+
+  AsteriskUnitTestMatrix:
+    needs: [ IdentifyBranches ]
+    if: ${{ needs.IdentifyBranches.outputs.branch_count > 0 && ( success() || failure() ) }}
+    continue-on-error: false
+    strategy:
+      fail-fast: false
+      matrix:
+        branch: ${{ fromJSON(needs.IdentifyBranches.outputs.branches) }}
+    runs-on: ubuntu-latest
+    steps:
+      - name: Run Unit Tests for branch ${{matrix.branch}}
+        uses: asterisk/asterisk-ci-actions/AsteriskUnitComposite@main
+        with:
+          asterisk_repo:     ${{github.repository}}
+          pr_number:         ${{env.PR_NUMBER}}
+          base_branch:       ${{matrix.branch}}
+          is_cherry_pick:    true
+          modules_blacklist: ${{env.MODULES_BLACKLIST}}
+          github_token:      ${{secrets.GITHUB_TOKEN}}
+          unittest_command:  ${{vars.UNITTEST_COMMAND}}
+
+  AsteriskUnitTests:
+    needs: [ IdentifyBranches, AsteriskUnitTestMatrix ]
+    if: ${{ needs.IdentifyBranches.outputs.branch_count > 0 && ( success() || failure() ) }}
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check unit test matrix status
+        env:
+          RESULT:    ${{needs.AsteriskUnitTestMatrix.result}}
+          GH_TOKEN:  ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          case $RESULT in
+            success)
+              gh pr edit --repo ${{github.repository}} \
+                --add-label ${{vars.CHERRY_PICK_CHECKS_PASSED_LABEL}} \
+                ${{env.PR_NUMBER}} || :
+              echo "::notice::All tests passed"
+              exit 0
+              ;;
+            skipped)
+              gh pr edit --repo ${{github.repository}} \
+                --remove-label ${{vars.CHERRY_PICK_TESTING_IN_PROGRESS}} \
+                --add-label ${{vars.CHERRY_PICK_CHECKS_FAILED_LABEL}} \
+                ${{env.PR_NUMBER}} || :
+              echo "::notice::Unit tests were skipped because of an earlier failure"
+              exit 1
+              ;;
+            *)
+              gh pr edit --repo ${{github.repository}} \
+                --remove-label ${{vars.CHERRY_PICK_TESTING_IN_PROGRESS}} \
+                --add-label ${{vars.CHERRY_PICK_CHECKS_FAILED_LABEL}} \
+                ${{env.PR_NUMBER}} || :
+              echo "::error::One or more tests failed ($RESULT)"
+              exit 1
+          esac
+
+  AsteriskGateTestMatrix:
+    needs: [ IdentifyBranches, AsteriskUnitTests ]
+    if: ${{ success() }}
+    continue-on-error: false
+    strategy:
+      fail-fast: false
+      matrix:
+        branch: ${{ fromJSON(needs.IdentifyBranches.outputs.branches) }}
+        group: ${{ fromJSON(vars.GATETEST_LIST) }}
+    runs-on: ubuntu-latest
+    steps:
+      - name: Run Gate Tests for ${{ matrix.group }}-${{matrix.branch}}
+        uses: asterisk/asterisk-ci-actions/AsteriskGateComposite@main
+        with:
+          test_type:         Gate
+          asterisk_repo:     ${{github.repository}}
+          pr_number:         ${{env.PR_NUMBER}}
+          base_branch:       ${{matrix.branch}}
+          is_cherry_pick:    true
+          modules_blacklist: ${{env.MODULES_BLACKLIST}}
+          github_token:      ${{secrets.GITHUB_TOKEN}}
+          testsuite_repo:    ${{vars.TESTSUITE_REPO}}
+          gatetest_group:    ${{matrix.group}}
+          gatetest_commands: ${{vars.GATETEST_COMMANDS}}
+
+  AsteriskGateTests:
+    needs: [ IdentifyBranches, AsteriskGateTestMatrix ]
+    if: ${{ success() || failure() }}
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check test matrix status
+        env:
+          RESULT:    ${{needs.AsteriskGateTestMatrix.result}}
+          GH_TOKEN:  ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          gh pr edit --repo ${{github.repository}} \
+            --remove-label ${{vars.CHERRY_PICK_TESTING_IN_PROGRESS}} \
+            ${{env.PR_NUMBER}} || :
+          case $RESULT in
+            success)
+              gh pr edit --repo ${{github.repository}} \
+                --add-label ${{vars.CHERRY_PICK_GATES_PASSED_LABEL}} \
+                ${{env.PR_NUMBER}} || :
+              echo "::notice::All Testsuite tests passed"
+              exit 0
+              ;;
+            skipped)
+              echo "::error::Testsuite tests were skipped because of an earlier failure"
+              exit 1
+              ;;
+            *)
+              gh pr edit --repo ${{github.repository}} \
+                --add-label ${{vars.CHERRY_PICK_GATES_FAILED_LABEL}} \
+                ${{env.PR_NUMBER}} || :
+              echo "::error::One or more Testsuite tests failed ($RESULT)"
+              exit 1
+          esac
diff --git a/.github/workflows/IssueOpened.yml b/.github/workflows/IssueOpened.yml
new file mode 100644
index 0000000000000000000000000000000000000000..74cef00b4dc9533fe18b58f4df090130a1429474
--- /dev/null
+++ b/.github/workflows/IssueOpened.yml
@@ -0,0 +1,15 @@
+name: Issue Opened
+run-name: "Issue ${{github.event.number}} ${{github.event.action}} by ${{github.actor}}"
+on:
+  issues:
+    types: opened
+
+jobs:
+  triage:
+    runs-on: ubuntu-latest
+    steps:
+      - name: initial labeling
+        uses: andymckay/labeler@master
+        with:
+          add-labels: "triage"
+          ignore-if-labeled: true
diff --git a/.github/workflows/NightlyAdmin.yml b/.github/workflows/NightlyAdmin.yml
new file mode 100644
index 0000000000000000000000000000000000000000..17948947a79b45c1bbb0578482bff3f6965056f6
--- /dev/null
+++ b/.github/workflows/NightlyAdmin.yml
@@ -0,0 +1,48 @@
+name: Nightly Admin
+on:
+  schedule:
+    - cron: '30 1 * * *'
+
+env:
+  ASTERISK_REPO:     ${{ github.repository }}
+  PR_NUMBER:         0
+  PR_COMMIT:         ''
+  GITHUB_TOKEN:      ${{ secrets.GITHUB_TOKEN }}
+  GH_TOKEN:          ${{ secrets.GITHUB_TOKEN }}
+  MODULES_BLACKLIST: ${{ vars.GATETEST_MODULES_BLACKLIST }} ${{ vars.UNITTEST_MODULES_BLACKLIST }}
+
+jobs:
+  CloseStaleIssues:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Close Stale Issues
+        uses: actions/stale@v7
+        with:
+          stale-issue-message: 'This issue is stale because it has been open 7 days with no activity. Remove stale label or comment or this will be closed in 14 days.'
+          stale-issue-label: stale
+          close-issue-message: 'This issue was closed because it has been stalled for 14 days with no activity.'
+          days-before-stale: 7
+          days-before-close: 14
+          days-before-pr-close: -1
+          only-label: triage,feedback-required
+
+  PublishWikiDocs:
+    if: ${{fromJSON(vars.WIKIDOCS_ENABLE) == true}}
+    strategy:
+      fail-fast: false
+      matrix:
+        branch: ${{ fromJSON(vars.WIKIDOC_BRANCHES) }}
+    runs-on: ubuntu-latest
+    steps:
+      - name: PublishWikiDocs
+        uses: asterisk/asterisk-ci-actions/AsteriskPublishDocsComposite@main
+        with:
+          asterisk_repo:     ${{env.ASTERISK_REPO}}
+          base_branch:       ${{matrix.branch}}
+          modules_blacklist: ${{env.MODULES_BLACKLIST}}
+          github_token:      ${{secrets.GITHUB_TOKEN}}
+          publish_docs_repo:   ${{vars.PUBLISH_DOCS_REPO}}
+          publish_docs_branch: ${{vars.PUBLISH_DOCS_BRANCH}}
+          confluence_url:      ${{vars.CONFLUENCE_URL}}
+          confluence_userpass: ${{secrets.CONFLUENCE_USERPASS}}
+          confluence_space:    ${{vars.CONFLUENCE_SPACE}}
diff --git a/.github/workflows/NightlyTests.yml b/.github/workflows/NightlyTests.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7eb3e163b7c3f988eb8ee1e95a9e1e3218e9ab38
--- /dev/null
+++ b/.github/workflows/NightlyTests.yml
@@ -0,0 +1,59 @@
+name: NightlyTests
+on:
+  workflow_dispatch:
+
+  schedule:
+    - cron: '0 2 * * *'
+
+env:
+  ASTERISK_REPO:     ${{ github.repository }}
+  PR_NUMBER:         0
+  PR_COMMIT:         ''
+  GITHUB_TOKEN:      ${{ secrets.GITHUB_TOKEN }}
+  GH_TOKEN:          ${{ secrets.GITHUB_TOKEN }}
+  MODULES_BLACKLIST: ${{ vars.GATETEST_MODULES_BLACKLIST }}
+
+jobs:
+  AsteriskNightly:
+    strategy:
+      fail-fast: false
+      matrix:
+        branch: ${{ fromJSON(vars.NIGHTLYTEST_BRANCHES) }}
+        group: ${{ fromJSON(vars.NIGHTLYTEST_LIST) }}
+    runs-on: ubuntu-latest
+    steps:
+      - name: Run Nightly Tests for ${{ matrix.group }}/${{ matrix.branch }}
+        uses: asterisk/asterisk-ci-actions/AsteriskGateComposite@main
+        with:
+          test_type:         Nightly
+          asterisk_repo:     ${{env.ASTERISK_REPO}}
+          pr_number:         ${{env.PR_NUMBER}}
+          base_branch:       ${{matrix.branch}}
+          modules_blacklist: ${{env.MODULES_BLACKLIST}}
+          github_token:      ${{secrets.GITHUB_TOKEN}}
+          testsuite_repo:    ${{vars.TESTSUITE_REPO}}
+          gatetest_group:    ${{matrix.group}}
+          gatetest_commands: ${{vars.GATETEST_COMMANDS}}
+
+  AsteriskNightlyTests:
+    if: ${{ always() }}
+    runs-on: ubuntu-latest
+    needs: AsteriskNightly
+    steps:
+      - name: Check test matrix status
+        env:
+          RESULT: ${{needs.AsteriskNightly.result}}
+        run: |
+          case $RESULT in
+            success)
+              echo "::notice::All Testsuite tests passed"
+              exit 0
+              ;;
+            skipped)
+              echo "::error::Testsuite tests were skipped because of an earlier failure"
+              exit 1
+              ;;
+              *)
+              echo "::error::One or more Testsuite tests failed"
+              exit 1
+          esac
diff --git a/.github/workflows/PRMerged.yml b/.github/workflows/PRMerged.yml
new file mode 100644
index 0000000000000000000000000000000000000000..73af62a49b6f4343835b8c357d0d06eb81967097
--- /dev/null
+++ b/.github/workflows/PRMerged.yml
@@ -0,0 +1,69 @@
+name: PRMerged
+run-name: "PR ${{github.event.number || inputs.pr_number}} ${{github.event.action || 'MANUAL POST MERGE'}} by ${{ github.actor }}"
+on:
+  pull_request_target:
+    types: [closed]
+  workflow_dispatch:
+    inputs:
+      pr_number:
+        description: 'PR number'
+        required: true
+        type: number
+
+concurrency:
+  group: ${{github.workflow}}-${{github.event.number || inputs.pr_number}}
+  cancel-in-progress: true
+
+env:
+  REPO:              ${{github.repository}}
+  PR_NUMBER:         ${{github.event.number || inputs.pr_number}}
+  GITHUB_TOKEN:      ${{secrets.GITHUB_TOKEN}}
+
+jobs:
+  CloseIssues:
+    if: github.event.pull_request.merged == true
+    runs-on: ubuntu-latest
+    steps:
+      - uses: wow-actions/auto-close-fixed-issues@v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  IdentifyBranches:
+    if: github.event.pull_request.merged == true || inputs.pr_number
+    outputs:
+      branches:     ${{ steps.getbranches.outputs.branches }}
+      branch_count: ${{ steps.getbranches.outputs.branch_count }}
+      github_token: ${{steps.get_workflow_token.outputs.token}}
+    runs-on: ubuntu-latest
+    steps:
+      - name: Get cherry-pick branches
+        uses: asterisk/asterisk-ci-actions/GetCherryPickBranchesFromPR@main
+        id: getbranches
+        with:
+          repo:              ${{env.REPO}}
+          pr_number:         ${{env.PR_NUMBER}}
+          cherry_pick_regex: ${{vars.CHERRY_PICK_REGEX}}
+          github_token:      ${{env.GITHUB_TOKEN}}
+
+  MergeCherryPicks:
+    needs: [ IdentifyBranches ]
+    if: needs.IdentifyBranches.outputs.branch_count > 0
+    continue-on-error: false
+    strategy:
+      fail-fast: true
+      matrix:
+        branch: ${{ fromJSON(needs.IdentifyBranches.outputs.branches) }}
+    runs-on: ubuntu-latest
+    steps:
+    
+      - name: Cherry Pick PR ${{env.PR_NUMBER}} to branch ${{matrix.branch}}
+        uses: asterisk/asterisk-ci-actions/CherryPick@main
+        with:
+          repo:              ${{env.REPO}}
+          pr_number:         ${{env.PR_NUMBER}}
+          branch:            ${{matrix.branch}}
+          github_token:      ${{secrets.ASTERISKTEAM_PAT}}
+          access_app_id:     ${{secrets.ASTERISK_ORG_ACCESS_APP_ID}}
+          access_app_key:    ${{secrets.ASTERISK_ORG_ACCESS_APP_PRIV_KEY}}
diff --git a/.github/workflows/PROpenedOrUpdated.yml b/.github/workflows/PROpenedOrUpdated.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9d04016d29db373dd9d60c8fe3843df77b0c307b
--- /dev/null
+++ b/.github/workflows/PROpenedOrUpdated.yml
@@ -0,0 +1,135 @@
+name: PROpenedOrUpdated
+run-name: "PR ${{github.event.number}} ${{github.event.action}} by ${{ github.actor }}"
+on:
+#  workflow_dispatch:
+  pull_request_target:
+    types: [opened, reopened, synchronize]
+
+#concurrency:
+#  group: ${{github.workflow}}-${{github.event.number}}
+#  cancel-in-progress: true
+
+env:
+  ASTERISK_REPO:     ${{github.repository}}
+  PR_NUMBER:         ${{github.event.number}}
+  PR_COMMIT:         ${{github.event.pull_request.head.sha}}
+  BRANCH:            ${{github.event.pull_request.base.ref}}
+  GITHUB_TOKEN:      ${{secrets.GITHUB_TOKEN}}
+  MODULES_BLACKLIST: ${{vars.GATETEST_MODULES_BLACKLIST}} ${{vars.UNITTEST_MODULES_BLACKLIST}}
+
+jobs:
+
+  AsteriskUnitTests:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Set Labels
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          gh pr edit --repo ${{github.repository}} \
+            --remove-label ${{vars.TEST_CHECKS_PASSED_LABEL}} \
+            --remove-label ${{vars.TEST_CHECKS_FAILED_LABEL}} \
+            --remove-label ${{vars.TEST_GATES_PASSED_LABEL}} \
+            --remove-label ${{vars.TEST_GATES_FAILED_LABEL}} \
+            --add-label ${{vars.TESTING_IN_PROGRESS}} \
+            ${{env.PR_NUMBER}} || :
+
+      - name: Run Unit Tests
+        uses: asterisk/asterisk-ci-actions/AsteriskUnitComposite@main
+        with:
+          asterisk_repo:     ${{env.ASTERISK_REPO}}
+          pr_number:         ${{env.PR_NUMBER}}
+          base_branch:       ${{env.BRANCH}}
+          modules_blacklist: ${{env.MODULES_BLACKLIST}}
+          github_token:      ${{secrets.GITHUB_TOKEN}}
+          unittest_command:  ${{vars.UNITTEST_COMMAND}}
+
+      - name: Get Token needed to add reviewers
+        if: ${{ success() }}
+        id: get_workflow_token
+        uses: peter-murray/workflow-application-token-action@v1
+        with:
+          application_id: ${{secrets.ASTERISK_ORG_ACCESS_APP_ID}}
+          application_private_key: ${{secrets.ASTERISK_ORG_ACCESS_APP_PRIV_KEY}}
+          organization: asterisk
+
+      - name: Add Reviewers
+        if: ${{ success() }}
+        env:
+          GITHUB_TOKEN: ${{steps.get_workflow_token.outputs.token}}
+          GH_TOKEN: ${{steps.get_workflow_token.outputs.token}}
+          REVIEWERS: ${{vars.PR_REVIEWERS}}
+        run: |
+          echo "${{env.GITHUB_ACTION}} Add reviewers"
+          IFS=$'; \n'
+          for r in $REVIEWERS ; do
+            gh pr edit --repo ${ASTERISK_REPO} ${PR_NUMBER} --add-reviewer $r
+          done
+          gh pr edit --repo ${{github.repository}} \
+            --add-label ${{vars.TEST_CHECKS_PASSED_LABEL}} \
+            ${{env.PR_NUMBER}} || :
+
+  AsteriskGate:
+    needs: AsteriskUnitTests
+    continue-on-error: false
+    strategy:
+      fail-fast: false
+      matrix:
+        group: ${{ fromJSON(vars.GATETEST_LIST) }}
+    runs-on: ubuntu-latest
+    steps:
+      - id: runtest
+        name: Run Gate Tests for ${{ matrix.group }}
+        uses: asterisk/asterisk-ci-actions/AsteriskGateComposite@main
+        with:
+          test_type:         Gate
+          asterisk_repo:     ${{env.ASTERISK_REPO}}
+          pr_number:         ${{env.PR_NUMBER}}
+          base_branch:       ${{env.BRANCH}}
+          modules_blacklist: ${{env.MODULES_BLACKLIST}}
+          github_token:      ${{secrets.GITHUB_TOKEN}}
+          testsuite_repo:    ${{vars.TESTSUITE_REPO}}
+          gatetest_group:    ${{matrix.group}}
+          gatetest_commands: ${{vars.GATETEST_COMMANDS}}
+
+
+  AsteriskGateTests:
+    name: AsteriskGateTests
+    if: always()
+    runs-on: ubuntu-latest
+    needs: AsteriskGate
+    steps:
+      - name: Check test matrix status
+        env:
+          RESULT: ${{ needs.AsteriskGate.result }}
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          echo "all results: ${{ toJSON(needs.*.result) }}"
+          echo "composite result: ${{ needs.AsteriskGate.result }}"
+
+          gh pr edit --repo ${{github.repository}} \
+            --remove-label ${{vars.TESTING_IN_PROGRESS}} \
+            ${{env.PR_NUMBER}} || :
+
+          case $RESULT in
+            success)
+              gh pr edit --repo ${{github.repository}} \
+                --add-label ${{vars.TEST_GATES_PASSED_LABEL}} \
+                ${{env.PR_NUMBER}} || :
+              echo "::notice::All Testsuite tests passed"
+              exit 0
+              ;;
+            skipped)
+              gh pr edit --repo ${{github.repository}} \
+                --add-label ${{vars.TEST_CHECKS_FAILED_LABEL}} \
+                ${{env.PR_NUMBER}} || :
+              echo "::error::Testsuite tests were skipped because of an earlier failure"
+              exit 1
+              ;;
+            *)
+              gh pr edit --repo ${{github.repository}} \
+                --add-label ${{vars.TEST_GATES_FAILED_LABEL}} \
+                ${{env.PR_NUMBER}} || :
+              echo "::error::One or more Testsuite tests failed ($RESULT)"
+              exit 1
+          esac
diff --git a/.gitignore b/.gitignore
index 8a4986d3b577daf66143c5e8b16a29a28a40c384..1ba75facf370eea54a5d36ee90146cdae025dafb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,8 +19,6 @@
 *.makeopts
 *.makedeps
 .lastclean
-.cproject
-.project
 /.pc
 aclocal.m4
 autom4te.cache
diff --git a/.gitreview b/.gitreview
deleted file mode 100644
index 56859fe88a4a9a7ea13596e9b64ef999f5a47a55..0000000000000000000000000000000000000000
--- a/.gitreview
+++ /dev/null
@@ -1,11 +0,0 @@
-[gerrit]
-defaultbranch=18
-basebranch=18
-#
-# Intentional padding to ensure it is possible to point a commit
-# to an alternative gerrit server/repository without breaking
-# cherry-pick between branches.
-#
-host=gerrit.asterisk.org
-port=29418
-project=asterisk.git
diff --git a/.version b/.version
index 0ec67166a2c292c7886b3a272ecfd049cbc3117b..4adfbc353177e768c953bd3e837b7507f8d9540c 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-18.11.2
+20.3.0
diff --git a/BUGS b/BUGS
index 624459ea52692d48b27e63ad438a05a776c59981..efb9bbd796cf61dd6e231055a1e8c6564d939387 100644
--- a/BUGS
+++ b/BUGS
@@ -1,22 +1,22 @@
 Asterisk Bug Tracking Information
 =================================
 
-To learn about and report Asterisk bugs, please visit 
+To learn about and report Asterisk bugs, please visit
 the official Asterisk Bug Tracker at:
 
-	https://issues.asterisk.org/jira
+	https://github.com/asterisk/asterisk/issues/
 
-For more information on using the bug tracker, or to 
+For more information on using the bug tracker, or to
 learn how you can contribute by acting as a bug marshal
 please see:
 
-	http://www.asterisk.org/developers/bug-guidelines
+	https://wiki.asterisk.org/wiki/x/RgAtAQ
 
 If you would like to submit a feature request, please
 resist the temptation to post it to the bug tracker.
 Feature requests should be posted to the asterisk-dev
 mailing list, located at:
 
-	http://lists.digium.com 
+	http://lists.digium.com
 
 Thank you!
diff --git a/CHANGES.md b/CHANGES.md
new file mode 120000
index 0000000000000000000000000000000000000000..f802810430b3a6732bdbf82bd46f9c15b8db7a86
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1 @@
+ChangeLogs/ChangeLog-20.3.0.md
\ No newline at end of file
diff --git a/ChangeLogs/ChangeLog-20.3.0.md b/ChangeLogs/ChangeLog-20.3.0.md
new file mode 100644
index 0000000000000000000000000000000000000000..5c6cd4ba37ba1018d1dd2b11bc06b28e48901ef3
--- /dev/null
+++ b/ChangeLogs/ChangeLog-20.3.0.md
@@ -0,0 +1,741 @@
+
+Change Log for Release 20.3.0
+========================================
+
+Summary:
+----------------------------------------
+
+- Set up new ChangeLogs directory
+- .github: Add AsteriskReleaser
+- chan_pjsip: also return all codecs on empty re-INVITE for late offers
+- cel: add local optimization begin event
+- core: Cleanup gerrit and JIRA references. (#57)
+- .github: Fix CherryPickTest to only run when it should
+- .github: Fix reference to CHERRY_PICK_TESTING_IN_PROGRESS
+- .github: Remove separate set labels step from new PR
+- .github: Refactor CP progress and add new PR test progress
+- res_pjsip: mediasec: Add Security-Client headers after 401
+- .github: Add cherry-pick test progress labels
+- LICENSE: Update link to trademark policy.
+- chan_dahdi: Add dialmode option for FXS lines.
+- .github: Update issue templates
+- .github: Remove unnecessary parameter in CherryPickTest
+- Initial GitHub PRs
+- Initial GitHub Issue Templates
+- pbx_dundi: Fix PJSIP endpoint configuration check.
+- Revert "app_queue: periodic announcement configurable start time."
+- res_pjsip_stir_shaken: Fix JSON field ordering and disallowed TN characters.
+- pbx_dundi: Add PJSIP support.
+- install_prereq: Add Linux Mint support.
+- chan_pjsip: fix music on hold continues after INVITE with replaces
+- voicemail.conf: Fix incorrect comment about #include.
+- app_queue: Fix minor xmldoc duplication and vagueness.
+- test.c: Fix counting of tests and add 2 new tests
+- res_calendar: output busy state as part of show calendar.
+- loader.c: Minor module key check simplification.
+- ael: Regenerate lexers and parsers.
+- bridge_builtin_features: add beep via touch variable
+- res_mixmonitor: MixMonitorMute by MixMonitor ID
+- format_sln: add .slin as supported file extension
+- res_agi: RECORD FILE plays 2 beeps.
+- func_json: Fix JSON parsing issues.
+- app_senddtmf: Add SendFlash AMI action.
+- app_dial: Fix DTMF not relayed to caller on unanswered calls.
+- configure: fix detection of re-entrant resolver functions
+- cli: increase channel column width
+- app_queue: periodic announcement configurable start time.
+- make_version: Strip svn stuff and suppress ref HEAD errors
+- res_http_media_cache: Introduce options and customize
+- main/iostream.c: fix build with libressl
+- contrib: rc.archlinux.asterisk uses invalid redirect.
+
+User Notes:
+----------------------------------------
+
+- ### cel: add local optimization begin event
+  The new AST_CEL_LOCAL_OPTIMIZE_BEGIN can be used
+  by itself or in conert with the existing
+  AST_CEL_LOCAL_OPTIMIZE to book-end local channel optimizaion.
+
+- ### chan_dahdi: Add dialmode option for FXS lines.
+  A "dialmode" option has been added which allows
+  specifying, on a per-channel basis, what methods of
+  subscriber dialing (pulse and/or tone) are permitted.
+  Additionally, this can be changed on a channel
+  at any point during a call using the CHANNEL
+  function.
+
+- ### pbx_dundi: Add PJSIP support.
+  DUNDi now supports chan_pjsip. Outgoing calls using
+  PJSIP require the pjsip_outgoing_endpoint option
+  to be set in dundi.conf.
+
+- ### cli: increase channel column width
+  This change increases the display width on 'core show channels'
+  amd 'core show channels verbose'
+  For 'core show channels', the Channel name field is increased to
+  64 characters and the Location name field is increased to 32
+  characters.
+  For 'core show channels verbose', the Channel name field is
+  increased to 80 characters, the Context is increased to 24
+  characters and the Extension is increased to 24 characters.
+
+- ### app_senddtmf: Add SendFlash AMI action.
+  The SendFlash AMI action now allows sending
+  a hook flash event on a channel.
+
+- ### res_http_media_cache: Introduce options and customize
+  The res_http_media_cache module now attempts to load
+  configuration from the res_http_media_cache.conf file.
+  The following options were added:
+    * timeout_secs
+    * user_agent
+    * follow_location
+    * max_redirects
+    * protocols
+    * redirect_protocols
+    * dns_cache_timeout_secs
+
+- ### test.c: Fix counting of tests and add 2 new tests
+  The "tests" attribute of the "testsuite" element in the
+  output XML now reflects only the tests actually requested
+  to be executed instead of all the tests registered.
+  The "failures" attribute was added to the "testsuite"
+  element.
+  Also added two new unit tests that just pass and fail
+  to be used for testing CI itself.
+
+- ### res_mixmonitor: MixMonitorMute by MixMonitor ID
+  It is now possible to specify the MixMonitorID when calling
+  the manager action: MixMonitorMute.  This will allow an
+  individual MixMonitor instance to be muted via ID.
+  The MixMonitorID can be stored as a channel variable using
+  the 'i' MixMonitor option and is returned upon creation if
+  this option is used.
+  As part of this change, if no MixMonitorID is specified in
+  the manager action MixMonitorMute, Asterisk will set the mute
+  flag on all MixMonitor audiohooks on the channel.  Previous
+  behavior would set the flag on the first MixMonitor audiohook
+  found.
+
+- ### bridge_builtin_features: add beep via touch variable
+  Add optional touch variable : TOUCH_MIXMONITOR_BEEP(interval)
+  Setting TOUCH_MIXMONITOR_BEEP/TOUCH_MONITOR_BEEP to a valid
+  interval in seconds will result in a periodic beep being
+  played to the monitored channel upon MixMontior/Monitor
+  feature start.
+  If an interval less than 5 seconds is specified, the interval
+  will default to 5 seconds.  If the value is set to an invalid
+  interval, the default of 15 seconds will be used.
+
+- ### format_sln: add .slin as supported file extension
+  format_sln now recognizes '.slin' as a valid
+  file extension in addition to the existing
+  '.sln' and '.raw'.
+
+
+Upgrade Notes:
+----------------------------------------
+
+- ### cel: add local optimization begin event
+  The existing AST_CEL_LOCAL_OPTIMIZE can continue
+  to be used as-is and the AST_CEL_LOCAL_OPTIMIZE_BEGIN event
+  can be ignored if desired.
+
+
+Closed Issues:
+----------------------------------------
+
+  - #35: [New Feature]: chan_dahdi: Allow disabling pulse or tone dialing
+  - #39: [Bug]: Remove .gitreview from repository.
+  - #43: [Bug]: Link to trademark policy is no longer correct
+  - #48: [bug]: res_pjsip: Mediasec requires different headers on 401 response
+  - #52: [improvement]: Add local optimization begin cel event
+
+Commits By Author:
+----------------------------------------
+
+- ### Asterisk Development Team (1):
+  - Update for 20.3.0-rc1
+
+- ### Fabrice Fontaine (2):
+  - main/iostream.c: fix build with libressl
+  - configure: fix detection of re-entrant resolver functions
+
+- ### George Joseph (13):
+  - make_version: Strip svn stuff and suppress ref HEAD errors
+  - test.c: Fix counting of tests and add 2 new tests
+  - Initial GitHub Issue Templates
+  - Initial GitHub PRs
+  - .github: Remove unnecessary parameter in CherryPickTest
+  - .github: Update issue templates
+  - .github: Add cherry-pick test progress labels
+  - .github: Refactor CP progress and add new PR test progress
+  - .github: Remove separate set labels step from new PR
+  - .github: Fix reference to CHERRY_PICK_TESTING_IN_PROGRESS
+  - .github: Fix CherryPickTest to only run when it should
+  - .github: Add AsteriskReleaser
+  - Set up new ChangeLogs directory
+
+- ### Henning Westerholt (2):
+  - chan_pjsip: fix music on hold continues after INVITE with replaces
+  - chan_pjsip: also return all codecs on empty re-INVITE for late offers
+
+- ### Holger Hans Peter Freyther (1):
+  - res_http_media_cache: Introduce options and customize
+
+- ### Jaco Kroon (2):
+  - app_queue: periodic announcement configurable start time.
+  - res_calendar: output busy state as part of show calendar.
+
+- ### Joshua C. Colp (2):
+  - pbx_dundi: Fix PJSIP endpoint configuration check.
+  - LICENSE: Update link to trademark policy.
+
+- ### Joshua Colp (1):
+  - Revert "app_queue: periodic announcement configurable start time."
+
+- ### Maximilian Fridrich (1):
+  - res_pjsip: mediasec: Add Security-Client headers after 401
+
+- ### Mike Bradeen (5):
+  - cli: increase channel column width
+  - format_sln: add .slin as supported file extension
+  - res_mixmonitor: MixMonitorMute by MixMonitor ID
+  - bridge_builtin_features: add beep via touch variable
+  - cel: add local optimization begin event
+
+- ### Naveen Albert (8):
+  - app_dial: Fix DTMF not relayed to caller on unanswered calls.
+  - app_senddtmf: Add SendFlash AMI action.
+  - func_json: Fix JSON parsing issues.
+  - app_queue: Fix minor xmldoc duplication and vagueness.
+  - voicemail.conf: Fix incorrect comment about #include.
+  - pbx_dundi: Add PJSIP support.
+  - res_pjsip_stir_shaken: Fix JSON field ordering and disallowed TN characters.
+  - chan_dahdi: Add dialmode option for FXS lines.
+
+- ### Sean Bright (5):
+  - contrib: rc.archlinux.asterisk uses invalid redirect.
+  - res_agi: RECORD FILE plays 2 beeps.
+  - ael: Regenerate lexers and parsers.
+  - loader.c: Minor module key check simplification.
+  - core: Cleanup gerrit and JIRA references. (#57)
+
+- ### The_Blode (1):
+  - install_prereq: Add Linux Mint support.
+
+
+Detail:
+----------------------------------------
+
+- ### Set up new ChangeLogs directory
+  Author: George Joseph  
+  Date:   2023-05-09  
+
+
+- ### .github: Add AsteriskReleaser
+  Author: George Joseph  
+  Date:   2023-05-05  
+
+
+- ### chan_pjsip: also return all codecs on empty re-INVITE for late offers
+  Author: Henning Westerholt  
+  Date:   2023-05-03  
+
+  We should also return all codecs on an re-INVITE without SDP for a
+  call that used late offer (e.g. no SDP in the initial INVITE, SDP
+  in the ACK). Bugfix for feature introduced in ASTERISK-30193
+  (https://issues.asterisk.org/jira/browse/ASTERISK-30193)
+
+  Migration from previous gerrit change that was not merged.
+
+
+- ### cel: add local optimization begin event
+  Author: Mike Bradeen  
+  Date:   2023-05-02  
+
+  The current AST_CEL_LOCAL_OPTIMIZE event is and has been
+  triggered on a local optimization end to serve as a flag
+  indicating the event occurred.  This change adds a second
+  AST_CEL_LOCAL_OPTIMIZE_BEGIN event for further detail.
+
+  Resolves: #52
+
+  UpgradeNote: The existing AST_CEL_LOCAL_OPTIMIZE can continue
+  to be used as-is and the AST_CEL_LOCAL_OPTIMIZE_BEGIN event
+  can be ignored if desired.
+
+  UserNote: The new AST_CEL_LOCAL_OPTIMIZE_BEGIN can be used
+  by itself or in conert with the existing
+  AST_CEL_LOCAL_OPTIMIZE to book-end local channel optimizaion.
+
+
+- ### core: Cleanup gerrit and JIRA references. (#57)
+  Author: Sean Bright  
+  Date:   2023-05-03  
+
+  * Remove .gitreview and switch to pulling the main asterisk branch
+    version from configure.ac instead.
+
+  * Replace references to JIRA with GitHub.
+
+  * Other minor cleanup found along the way.
+
+  Resolves: #39
+
+- ### .github: Fix CherryPickTest to only run when it should
+  Author: George Joseph  
+  Date:   2023-05-03  
+
+  Fixed CherryPickTest so it triggers only on the
+  "cherry-pick-test" label instead of all labels.
+
+
+- ### .github: Fix reference to CHERRY_PICK_TESTING_IN_PROGRESS
+  Author: George Joseph  
+  Date:   2023-05-02  
+
+
+- ### .github: Remove separate set labels step from new PR
+  Author: George Joseph  
+  Date:   2023-05-02  
+
+
+- ### .github: Refactor CP progress and add new PR test progress
+  Author: George Joseph  
+  Date:   2023-05-02  
+
+
+- ### res_pjsip: mediasec: Add Security-Client headers after 401
+  Author: Maximilian Fridrich  
+  Date:   2023-05-02  
+
+  When using mediasec, requests sent after a 401 must still contain the
+  Security-Client header according to
+  draft-dawes-sipcore-mediasec-parameter.
+
+  Resolves: #48
+
+- ### .github: Add cherry-pick test progress labels
+  Author: George Joseph  
+  Date:   2023-05-02  
+
+
+- ### LICENSE: Update link to trademark policy.
+  Author: Joshua C. Colp  
+  Date:   2023-05-01  
+
+  Resolves: #43
+
+- ### chan_dahdi: Add dialmode option for FXS lines.
+  Author: Naveen Albert  
+  Date:   2023-04-28  
+
+  Currently, both pulse and tone dialing are always enabled
+  on all FXS lines, with no way of disabling one or the other.
+
+  In some circumstances, it is desirable or necessary to
+  disable one of these, and this behavior can be problematic.
+
+  A new "dialmode" option is added which allows setting the
+  methods to support on a per channel basis for FXS (FXO
+  signalled lines). The four options are "both", "pulse",
+  "dtmf"/"tone", and "none".
+
+  Additionally, integration with the CHANNEL function is
+  added so that this setting can be updated for a channel
+  during a call.
+
+  Resolves: #35
+  ASTERISK-29992
+
+  UserNote: A "dialmode" option has been added which allows
+  specifying, on a per-channel basis, what methods of
+  subscriber dialing (pulse and/or tone) are permitted.
+
+  Additionally, this can be changed on a channel
+  at any point during a call using the CHANNEL
+  function.
+
+
+- ### .github: Update issue templates
+  Author: George Joseph  
+  Date:   2023-05-01  
+
+
+- ### .github: Remove unnecessary parameter in CherryPickTest
+  Author: George Joseph  
+  Date:   2023-05-01  
+
+
+- ### Initial GitHub PRs
+  Author: George Joseph  
+  Date:   2023-04-28  
+
+
+- ### Initial GitHub Issue Templates
+  Author: George Joseph  
+  Date:   2023-04-28  
+
+
+- ### pbx_dundi: Fix PJSIP endpoint configuration check.
+  Author: Joshua C. Colp  
+  Date:   2023-04-13  
+
+  ASTERISK-28233
+
+
+- ### Revert "app_queue: periodic announcement configurable start time."
+  Author: Joshua Colp  
+  Date:   2023-04-11  
+
+  This reverts commit 3fd0b65bae4b1b14434737ffcf0da4aa9ff717f6.
+
+  Reason for revert: Causes segmentation fault.
+
+
+- ### res_pjsip_stir_shaken: Fix JSON field ordering and disallowed TN characters.
+  Author: Naveen Albert  
+  Date:   2023-02-17  
+
+  The current STIR/SHAKEN signing process is inconsistent with the
+  RFCs in a couple ways that can cause interoperability issues.
+
+  RFC8225 specifies that the keys must be ordered lexicographically, but
+  currently the fields are simply ordered according to the order
+  in which they were added to the JSON object, which is not
+  compliant with the RFC and can cause issues with some carriers.
+
+  To fix this, we now leverage libjansson's ability to dump a JSON
+  object sorted by key value, yielding the correct field ordering.
+
+  Additionally, telephone numbers must have any leading + prefix removed
+  and must not contain characters outside of 0-9, *, and # in order
+  to comply with the RFCs. Numbers are now properly formatted as such.
+
+  ASTERISK-30407 #close
+
+
+- ### pbx_dundi: Add PJSIP support.
+  Author: Naveen Albert  
+  Date:   2022-12-09  
+
+  Adds PJSIP as a supported technology to DUNDi.
+
+  To facilitate this, we now allow an endpoint to be specified
+  for outgoing PJSIP calls. We also allow users to force a specific
+  channel technology for outgoing SIP-protocol calls.
+
+  ASTERISK-28109 #close
+  ASTERISK-28233 #close
+
+
+- ### install_prereq: Add Linux Mint support.
+  Author: The_Blode  
+  Date:   2023-03-17  
+
+  ASTERISK-30359 #close
+
+
+- ### chan_pjsip: fix music on hold continues after INVITE with replaces
+  Author: Henning Westerholt  
+  Date:   2023-03-21  
+
+  In a three party scenario with INVITE with replaces, we need to
+  unhold the call, otherwise one party continues to get music on
+  hold, and the call is not properly bridged between them.
+
+  ASTERISK-30428
+
+
+- ### voicemail.conf: Fix incorrect comment about #include.
+  Author: Naveen Albert  
+  Date:   2023-03-28  
+
+  A comment at the top of voicemail.conf says that #include
+  cannot be used in voicemail.conf because this breaks
+  the ability for app_voicemail to auto-update passwords.
+  This is factually incorrect, since Asterisk has no problem
+  updating files that are #include'd in the main configuration
+  file, and this does work in voicemail.conf as well.
+
+  ASTERISK-30479 #close
+
+
+- ### app_queue: Fix minor xmldoc duplication and vagueness.
+  Author: Naveen Albert  
+  Date:   2023-04-03  
+
+  The F option in the xmldocs for the Queue application
+  was erroneously duplicated, causing it to display
+  twice on the wiki. The two sections are now merged into one.
+
+  Additionally, the description for the d option was quite
+  vague. Some more details are added to provide context
+  as to what this actually does.
+
+  ASTERISK-30486 #close
+
+
+- ### test.c: Fix counting of tests and add 2 new tests
+  Author: George Joseph  
+  Date:   2023-03-28  
+
+  The unit test XML output was counting all registered tests as "run"
+  even when only a subset were actually requested to be run and
+  the "failures" attribute was missing.
+
+  * The "tests" attribute of the "testsuite" element in the
+    output XML now reflects only the tests actually requested
+    to be executed instead of all the tests registered.
+
+  * The "failures" attribute was added to the "testsuite"
+    element.
+
+  Also added 2 new unit tests that just pass and fail to be
+  used for CI testing.
+
+
+- ### res_calendar: output busy state as part of show calendar.
+  Author: Jaco Kroon  
+  Date:   2023-03-23  
+
+  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+- ### loader.c: Minor module key check simplification.
+  Author: Sean Bright  
+  Date:   2023-03-23  
+
+
+- ### ael: Regenerate lexers and parsers.
+  Author: Sean Bright  
+  Date:   2023-03-21  
+
+  Various changes to ensure that the lexers and parsers can be correctly
+  generated when REBUILD_PARSERS is enabled.
+
+  Some notes:
+
+  * Because of the version of flex we are using to generate the lexers
+    (2.5.35) some post-processing in the Makefile is still required.
+
+  * The generated lexers do not contain the problematic C99 check that
+    was being replaced by the call to sed in the respective Makefiles so
+    it was removed.
+
+  * Since these files are generated, they will include trailing
+    whitespace in some places. This does not need to be corrected.
+
+
+- ### bridge_builtin_features: add beep via touch variable
+  Author: Mike Bradeen  
+  Date:   2023-03-01  
+
+  Add periodic beep option to one-touch recording by setting
+  the touch variable TOUCH_MONITOR_BEEP or
+  TOUCH_MIXMONITOR_BEEP to the desired interval in seconds.
+
+  If the interval is less than 5 seconds, a minimum of 5
+  seconds will be imposed.  If the interval is set to an
+  invalid value, it will default to 15 seconds.
+
+  A new test event PERIODIC_HOOK_ENABLED was added to the
+  func_periodic_hook hook_on function to indicate when
+  a hook is started.  This is so we can test that the touch
+  variable starts the hook as expected.
+
+  ASTERISK-30446
+
+
+- ### res_mixmonitor: MixMonitorMute by MixMonitor ID
+  Author: Mike Bradeen  
+  Date:   2023-03-13  
+
+  While it is possible to create multiple mixmonitor instances
+  on a channel, it was not previously possible to mute individual
+  instances.
+
+  This change includes the ability to specify the MixMonitorID
+  when calling the manager action: MixMonitorMute.  This will
+  allow an individual MixMonitor instance to be muted via id.
+  This id can be stored as a channel variable using the 'i'
+  MixMonitor option.
+
+  As part of this change, if no MixMonitorID is specified in
+  the manager action MixMonitorMute, Asterisk will set the mute
+  flag on all MixMonitor spy-type audiohooks on the channel.
+  This is done via the new audiohook function:
+  ast_audiohook_set_mute_all.
+
+  ASTERISK-30464
+
+
+- ### format_sln: add .slin as supported file extension
+  Author: Mike Bradeen  
+  Date:   2023-03-14  
+
+  Adds '.slin' to existing supported file extensions:
+  .sln and .raw
+
+  ASTERISK-30465
+
+
+- ### res_agi: RECORD FILE plays 2 beeps.
+  Author: Sean Bright  
+  Date:   2023-03-08  
+
+  Sending the "RECORD FILE" command without the optional
+  `offset_samples` argument can result in two beeps playing on the
+  channel.
+
+  This bug has been present since Asterisk 0.3.0 (2003-02-06).
+
+  ASTERISK-30457 #close
+
+
+- ### func_json: Fix JSON parsing issues.
+  Author: Naveen Albert  
+  Date:   2023-02-26  
+
+  Fix issue with returning empty instead of dumping
+  the JSON string when recursing.
+
+  Also adds a unit test to capture this fix.
+
+  ASTERISK-30441 #close
+
+
+- ### app_senddtmf: Add SendFlash AMI action.
+  Author: Naveen Albert  
+  Date:   2023-02-26  
+
+  Adds an AMI action to send a flash event
+  on a channel.
+
+  ASTERISK-30440 #close
+
+
+- ### app_dial: Fix DTMF not relayed to caller on unanswered calls.
+  Author: Naveen Albert  
+  Date:   2023-03-04  
+
+  DTMF frames are not handled in app_dial when sent towards the
+  caller. This means that if DTMF is sent to the calling party
+  and the call has not yet been answered, the DTMF is not audible.
+  This is now fixed by relaying DTMF frames if only a single
+  destination is being dialed.
+
+  ASTERISK-29516 #close
+
+
+- ### configure: fix detection of re-entrant resolver functions
+  Author: Fabrice Fontaine  
+  Date:   2023-03-08  
+
+  uClibc does not provide res_nsearch:
+  asterisk-16.0.0/main/dns.c:506: undefined reference to `res_nsearch'
+
+  Patch coded by Yann E. MORIN:
+  http://lists.busybox.net/pipermail/buildroot/2018-October/232630.html
+
+  ASTERISK-21795 #close
+
+  Signed-off-by: Bernd Kuhls <bernd.kuhls@t-online.de>
+  [Retrieved from:
+  https: //git.buildroot.net/buildroot/tree/package/asterisk/0005-configure-fix-detection-of-re-entrant-resolver-funct.patch]
+  Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
+
+- ### cli: increase channel column width
+  Author: Mike Bradeen  
+  Date:   2023-03-06  
+
+  For 'core show channels', the Channel name field is increased
+  to 64 characters and the Location name field is increased to
+  32 characters.
+
+  For 'core show channels verbose', the Channel name field is
+  increased to 80 characters, the Context is increased to 24
+  characters and the Extension is increased to 24 characters.
+
+  ASTERISK-30455
+
+
+- ### app_queue: periodic announcement configurable start time.
+  Author: Jaco Kroon  
+  Date:   2023-02-21  
+
+  This newly introduced periodic-announce-startdelay makes it possible to
+  configure the initial start delay of the first periodic announcement
+  after which periodic-announce-frequency takes over.
+
+  ASTERISK-30437 #close
+  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+- ### make_version: Strip svn stuff and suppress ref HEAD errors
+  Author: George Joseph  
+  Date:   2023-03-13  
+
+  * All of the code that used subversion has been removed.
+
+  * When Asterisk is checked out from a tag or commit instead
+    of one of the regular branches, git would emit messages like
+    "fatal: ref HEAD is not a symbolic ref" which weren't fatal
+    at all.  Those are now suppressed.
+
+
+- ### res_http_media_cache: Introduce options and customize
+  Author: Holger Hans Peter Freyther  
+  Date:   2022-10-16  
+
+  Make the existing CURL parameters configurable and allow
+  to specify the usable protocols, proxy and DNS timeout.
+
+  ASTERISK-30340
+
+
+- ### main/iostream.c: fix build with libressl
+  Author: Fabrice Fontaine  
+  Date:   2023-02-25  
+
+  Fix the following build failure with libressl by using SSL_is_server
+  which is available since version 2.7.0 and
+  https://github.com/libressl-portable/openbsd/commit/d7ec516916c5eaac29b02d7a8ac6570f63b458f7:
+
+  iostream.c: In function 'ast_iostream_close':
+  iostream.c:559:41: error: invalid use of incomplete typedef 'SSL' {aka 'struct ssl_st'}
+    559 |                         if (!stream->ssl->server) {
+        |                                         ^~
+
+  ASTERISK-30107 #close
+
+  Fixes: - http://autobuild.buildroot.org/results/ce4d62d00bb77ba5b303cacf6be7e350581a62f9
+
+- ### contrib: rc.archlinux.asterisk uses invalid redirect.
+  Author: Sean Bright  
+  Date:   2023-03-02  
+
+  `rc.archlinux.asterisk`, which explicitly requests bash in its
+  shebang, uses the following command syntax:
+
+    ${DAEMON} -rx "core stop now" > /dev/null 2&>1
+
+  The intent of which is to execute:
+
+    ${DAEMON} -rx "core stop now"
+
+  While sending both stdout and stderr to `/dev/null`. Unfortunately,
+  because the `&` is in the wrong place, bash is interpreting the `2` as
+  just an additional argument to the `$DAEMON` command and not as a file
+  descriptor and proceeds to use the bashism `&>` to send stderr and
+  stdout to a file named `1`.
+
+  So we clean it up and just use bash's shortcut syntax.
+
+  Issue raised and a fix suggested (but not used) by peutch on GitHub¹.
+
+  ASTERISK-30449 #close
+
+  1. https://github.com/asterisk/asterisk/pull/31
+
+
diff --git a/CHANGES b/ChangeLogs/historical/CHANGES
similarity index 96%
rename from CHANGES
rename to ChangeLogs/historical/CHANGES
index f0b2f56b8ec7c4ca27b758d54bb4abad18814415..401a886c62a1ee554248fac2df15b4a0a65bd047 100644
--- a/CHANGES
+++ b/ChangeLogs/historical/CHANGES
@@ -13,77 +13,315 @@
 ==============================================================================
 
 ------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.11.1 to Asterisk 18.11.2 ----------
+--- Functionality changes from Asterisk 20.1.0 to Asterisk 20.2.0 ------------
 ------------------------------------------------------------------------------
 
-func_odbc
+app_broadcast
 ------------------
- * A SQL_ESC_BACKSLASHES dialplan function has been added which
-   escapes backslashes. Usage of this is dependent on whether the
-   database in use can use backslashes to escape ticks or not. If
-   it can, then usage of this prevents a broken SQL query depending
-   on how the SQL query is constructed.
+ * A Broadcast application is now available which allows
+   for asynchronous one-to-many and many-to-one channel audio.
+
+app_directory
+------------------
+ * A new option 's' has been added to the Directory() application that
+   will skip calling the extension and instead set the extension as
+   DIRECTORY_EXTEN channel variable.
+
+app_read
+------------------
+ * A new option 'e' has been added to allow Read() to return the
+   terminator as the dialed digits in the case where only the terminator
+   is entered.
+
+app_senddtmf
+------------------
+ * A new option has been added to SendDTMF() which will answer the
+   specified channel if it is not already up. If no channel is specified,
+   the current channel will be answered instead.
+
+app_signal
+------------------
+ * Adds Signal and WaitForSignal applications
+   which can be used for signaling or as a
+   simple message queue in the dialplan.
+
+func_json
+------------------
+ * Additional parsing capabilities have been added to the
+   JSON_DECODE function, including support for arrays
+   and recursive indexing.
+
+res_phoneprov
+------------------
+ * On multihomed Asterisk servers with dynamic SERVER template variables,
+   reloading this module is no longer required when re-provisioning your
+   phone to another interface address (e.g. when moving between VLANs.)
+
+res_pjsip_rfc3326
+------------------
+ * Add ability to set HANGUPCAUSE when SIP causecode received in BYE Reason header (in
+   addition to currently supported Q.850). The first header found will be used to set
+   the HANGUPCAUSE variable.
+
+res_pjsip_session
+------------------
+ * The overlap_context option now allows explicitly
+   specifying a context to use for overlap dialing matches.
+
+res_rtp_asterisk
+------------------
+ * This module has been updated to provide additional
+   quality statistics in the form of an Asterisk
+   Media Experience Score.  The score is available using
+   the same mechanisms you'd use to retrieve jitter, loss,
+   and rtt statistics.  For more information about the
+   score and how to retrieve it, see
+   https://wiki.asterisk.org/wiki/display/AST/Media+Experience+Score
 
 ------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.10.0 to Asterisk 18.11.0 ----------
+--- Functionality changes from Asterisk 20.0.0 to Asterisk 20.1.0 ------------
 ------------------------------------------------------------------------------
 
-ami
+AMI
 ------------------
- * AMI events can now be globally disabled using
-   the disabledevents [general] setting.
+ * The AOCMessage action can now be used to generate AOC-S messages.
 
-app_mf
+Add support for named capture agent.
 ------------------
- * Adds an option to ReceiveMF to cap the
-   number of digits read at a user-specified
-   maximum.
+ * A name for the capture agent can now be specified
+   using the capture_name option which, if specified,
+   will be sent to the HEP server.
 
-app_queue
+app_if
 ------------------
- * Load queues and members from Realtime for
-   AMI actions: QueuePause, QueueStatus and QueueSummary,
-   Applications: PauseQueueMember and UnpauseQueueMember.
+ * Adds the If, ElseIf, Else, EndIf, and ExitIf applications
+   for conditional execution of a block of code.
 
- * Added a new AMI action: QueueWithdrawCaller
-   This AMI action makes it possible to withdraw a caller from a queue
-   back to the dialplan. The call will be signaled to leave the queue
-   whenever it can, hence, it not guaranteed that the call will leave
-   the queue.
+app_mixmonitor
+------------------
+ * The d option for MixMonitor now allows deleting
+   the original recording when MixMonitor exits,
+   which can be useful when MixMonitor copies it
+   somewhere else before exiting.
 
-   Optional custom data can be passed in the request, in the WithdrawInfo
-   parameter. If the call successfully withdrawn the queue,
-   it can be retrieved using the QUEUE_WITHDRAW_INFO variable.
+ * Adds the c option to use the real Caller ID on
+   the channel in voicemail recordings as opposed
+   to the Connected Line.
 
-   This can be useful for certain uses, such as dispatching the call
-   to a specific extension.
+app_voicemail
+------------------
+ * The voicemail user option attachextrecs can
+   now be set to control whether external recordings
+   trigger voicemail email notifications.
 
-channel_internal_api
+cdr
 ------------------
- * CHANNEL(lastcontext) and CHANNEL(lastexten)
-   are now available for use in the dialplan.
+ * Two new options have been added which allow
+   bridging and dial state changes to be ignored
+   in CDRs, which can be useful if a single CDR
+   is desired for a channel.
 
-res_pjsip_pubsub
+chan_dahdi
 ------------------
- * A new resource_list option, resource_display_name, indicates
-   whether display name of resource or the resource name being
-   provided for RLS entries.
-   If this option is enabled, the Display Name will be provided.
-   This option is disabled by default to remain the previous behavior.
-   If the 'event' set to 'presence' or 'dialog' the non-empty HINT name
-   will be set as the Display Name.
-   The 'message-summary' is not supported yet.
+ * FXO channels (FXS signaled) that don't use callerid or
+   distinctive ring detection can now be configured
+   to enter the dialplan immediately using immediate=yes,
+   instead of waiting for at least one ring.
 
- * The Resource List Subscriptions (RLS) is dynamic now.
-   The asterisk now updates current subscriptions to reflect the changes
-   to the list on subscription refresh. If list items are added,
-   removed, updated or do not exist anymore, the asterisk regenerates
-   the resource list.
+pbx_builtins
+------------------
+ * It is now possible to not wait for media on
+   a channel when answering it using Answer,
+   by specifying the i option.
+
+res_pjsip
+------------------
+ * Added options "security_negotiation" and "security_mechanisms" to pjsip
+   endpoints and registrations. "security_negotiation" can be set to "no" (default)
+   or "mediasec", and "security_mechanisms" can be a list of comma-separated
+   security_mechanisms in the form defined by RFC 3329 section 2.2.
+
+ * A new option named "all_codecs_on_empty_reinvite" has been added to the
+   global section. When this option is enabled, on reception of a re-INVITE
+   without SDP, Asterisk will send an SDP offer in the 200 OK response containing
+   all configured codecs on the endpoint, instead of simply those that have
+   already been negotiated. RFC 3261 specifies this as a SHOULD requirement.
+   The default value is "off".
+
+res_pjsip_aoc
+------------------
+ * Added res_pjsip_aoc which gives chan_pjsip the ability to send Advice-of-Charge messages.
+   A new endpoint option, send_aoc, controls this.
+
+res_pjsip_header_funcs
+------------------
+ * The new PJSIP_HEADER_PARAM function now fully supports both
+   URI and header parameters. Both reading and writing
+   parameters are supported.
+
+res_pjsip_logger
+------------------
+ * SIP messages can now be filtered by SIP request method
+   (INVITE, CANCEL, ACK, BYE, REGISTER, OPTION,
+   SUBSCRIBE, NOTIFY, PUBLISH, INFO, and MESSAGE),
+   allowing for more granular debugging to be done
+   in the CLI. This applies to requests but not responses.
+
+res_pjsip_notify
+------------------
+ * Allows using the config options in pjsip_notify.conf
+   from AMI actions as with the existing CLI commands.
+
+res_tonedetect
+------------------
+ * The TONE_DETECT function now supports
+   detection of audible ringback tone
+   using the p option.
+
+xmldocs
+------------------
+ * The XML documentation can now be reloaded without restarting
+   Asterisk, which makes it possible to load new modules that
+   enforce documentation without restarting Asterisk.
+
+------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 19.0.0 to Asterisk 20.0.0 ------------
+------------------------------------------------------------------------------
+
+New EXPORT function
+------------------
+ * A new function, EXPORT, allows writing variables
+   and functions on other channels, the complement
+   of the IMPORT function.
+
+app_amd
+------------------
+ * An audio file to play during AMD processing can
+   now be specified to the AMD application or configured
+   in the amd.conf configuration file.
+
+app_bridgewait
+------------------
+ * Adds the n option to not answer the channel when
+   the BridgeWait application is called.
+
+features
+------------------
+ * The Bridge application now has the n "no answer" option
+   that can be used to prevent the channel from being
+   automatically answered prior to bridging.
+
+func_strings
+------------------
+ * Three new functions, TRIM, LTRIM, and RTRIM, are
+   now available for trimming leading and trailing
+   whitespace.
+
+res_pjsip
+------------------
+ * A new option named "peer_supported" has been added to the endpoint option
+   100rel. When set to this option, Asterisk sends provisional responses
+   reliably if the peer supports it. If the peer does not support reliable
+   provisional responses, Asterisk sends them normally.
 
 ------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.9.0 to Asterisk 18.10.0 -----------
+--- Functionality changes from Asterisk 19.0.0 to Asterisk 20.0.0 ------------
 ------------------------------------------------------------------------------
 
+Transfer feature
+------------------
+ * The following capabilities have been added to the
+   transfer feature:
+
+   - The transfer initiation announcement prompt can
+   now be customized in features.conf.
+
+   - The TRANSFER_EXTEN variable now can be set on the
+   transferer's channel in order to allow the transfer
+   function to automatically attempt to go to the extension
+   contained in this variable, if it exists. The transfer
+   context behavior is not changed (TRANSFER_CONTEXT is used
+   if it exists; otherwise the default context is used).
+
+app_confbridge
+------------------
+ * Adds the end_marked_any option which can be used
+   to kick users from a conference after any
+   marked user leaves (including marked users).
+
+db
+------------------
+ * The DBPrefixGet AMI action now allows retrieving
+   all of the DB keys beginning with a particular
+   prefix.
+
+locks
+------------------
+ * A new AMI event, DeadlockStart, is now available
+   when Asterisk is compiled with DETECT_DEADLOCKS,
+   and can indicate that a deadlock has occured.
+
+res_geolocation
+------------------
+ * * Added processing for the 'confidence' element.
+   * Added documentation to some APIs.
+   * removed a lot of complex code related to the very-off-nominal
+     case of needing to process multiple location info sources.
+   * Create a new 'ast_geoloc_eprofile_to_pidf' API that just takes
+     one eprofile instead of a datastore of multiples.
+   * Plugged a huge leak in XML processing that arose from
+     insufficient documentation by the libxml/libxslt authors.
+   * Refactored stylesheets to be more efficient.
+   * Renamed 'profile_action' to 'profile_precedence' to better
+     reflect it's purpose.
+   * Added the config option for 'allow_routing_use' which
+     sets the value of the 'Geolocation-Routing' header.
+   * Removed the GeolocProfileCreate and GeolocProfileDelete
+     dialplan apps.
+   * Changed the GEOLOC_PROFILE dialplan function as follows:
+     * Removed the 'profile' argument.
+     * Automatically create a profile if it doesn't exist.
+     * Delete a profile if 'inheritable' is set to no.
+   * Fixed various bugs and leaks
+   * Updated Asterisk WiKi documentation.
+
+   Added 4 built-in profiles:
+     "<prefer_config>"
+     "<discard_config>"
+     "<prefer_incoming>"
+     "<discard_incoming>"
+   The profiles are empty except for having their precedence
+   set.
+
+   Added profile parameter "suppress_empty_ca_elements" that
+   will cause Civic Address elements that are empty to be
+   suppressed from the outgoing PIDF-LO document.
+
+   You can now specify the location object's format, location_info,
+   method, location_source and confidence parameters directly on
+   a profile object for simple scenarios where the location
+   information isn't common with any other profiles.  This is
+   mutually exclusive with setting location_reference on the
+   profile.
+
+   Added an 'a' option to the GEOLOC_PROFILE function to allow
+   variable lists like location_info_refinement to be appended
+   to instead of replacing the entire list.
+
+   Added an 'r' option to the GEOLOC_PROFILE function to resolve all
+   variables before a read operation and after a Set operation.
+
+res_musiconhold_answeredonly
+------------------
+ * This change adds an option, answeredonly, that will prevent music
+   on hold on channels that are not answered.
+
+res_pjsip
+------------------
+ * TLS transports in res_pjsip can now reload their TLS certificate
+   and private key files, provided the filename of them has not
+   changed.
+
 Applications
 ------------------
  * added support for Danish syntax, playing the correct plural sound file
@@ -94,6 +332,13 @@ Applications
    so it is avalible in b(content^extension^line)
    this add the same behaviour as Dial
 
+Channel-agnostic MF support
+------------------
+ * A SendMF application and PlayMF manager
+   application are now included to send
+   arbitrary standard R1 MF tones on the
+   current channel or another specified channel.
+
 Core
 ------------------
  * Bundled PJProject Build
@@ -103,79 +348,88 @@ Core
    https://wiki.asterisk.org/wiki/display/AST/Bundled+PJProject
    for more info.
 
-ami
+Handle non-standard Meter metric type safely
 ------------------
- * An AMI event now exists for "Wink".
+ * A meter_support flag has been introduced that defaults to true to maintain current behaviour.
+   If disabled, a counter metric type will be used instead wherever a meter metric type was used,
+   the counter will have a "_meter" suffix appended to the metric name.
 
-app_mf
+MessageSend
 ------------------
- * Adds MF receiver and sender applications to support
-   the R1 MF signaling protocol, including integration
-   with the Dial application.
+ * The MessageSend AMI action has been updated to allow the Destination
+   and the To addresses to be provided separately. This brings the
+   MessageSend manager command in line with the capabilities of the
+   MessageSend dialplan application.
 
-app_queue
+ToneScan application
 ------------------
- * added that we set DIALEDPEERNUMBER on the outgoing channels
-   so it is avalible in b(content^extension^line)
-   this add the same behaviour as Dial
+ * A new application, ToneScan, allows for
+   synchronous detection of call progress
+   signals such as dial tone, busy tone,
+   Special Information Tones, and modems.
 
-app_queues
+ami
 ------------------
- * adding support for playing the correct en/et for nordic languages
+ * An AMI event now exists for "Wink".
 
- * Don't play sound_thanks if there is no leading hold_time message
-   When the only announcement is hold time, and there is no hold time (0 min, 0 sec), asterisk will say "thank you for your patience"
+ * AMI events can now be globally disabled using
+   the disabledevents [general] setting.
 
-app_sendtext
+app_confbridge
 ------------------
- * A ReceiveText application has been added that can be
-   used in conjunction with the SendText application.
+ * Added the hear_own_join_sound option to the confbridge user profile to
+   control who hears the sound_join audio file. When set to 'yes' the user
+   entering the conference and the participants already in the conference
+   will hear the sound_join audio file. When set to 'no' the user entering
+   the conference will not hear the sound_join audio file, but the
+   participants already in the conference will hear the sound_join audio file.
 
-app_voicemail
-------------------
- * added support for Danish syntax, playing the correct plural sound file
-   dependen on where you have 1 or multipe messages
-   based on the existing SE/NO code
+ * Adds the CONFBRIDGE_CHANNELS function which can
+   be used to retrieve a list of channels in a ConfBridge,
+   optionally filtered by a particular category. This
+   list can then be used with functions like SHIFT, POP,
+   UNSHIFT, etc.
 
-cdr
+app_dtmfstore
 ------------------
- * A new CDR option, channeldefaultenabled, allows controlling
-   whether CDR is enabled or disabled by default on
-   newly created channels. The default behavior remains
-   unchanged from previous versions of Asterisk (new
-   channels will have CDR enabled, as long as CDR is
-   enabled globally).
+ * New application which collects digits
+   dialed and stores them into
+   a specified variable.
 
-chan_sip.c
+app_mf
 ------------------
- * resolve issue with pickup on device that uses "183" and not "180"
+ * Adds MF receiver and sender applications to support
+   the R1 MF signaling protocol, including integration
+   with the Dial application.
 
-cli
-------------------
- * The "module refresh" command has been added,
-   which allows unloading and then loading a
-   module with a single command.
+ * Adds an option to ReceiveMF to cap the
+   number of digits read at a user-specified
+   maximum.
 
-func_json
+app_milliwatt
 ------------------
- * The JSON_DECODE dialplan function can now be used
-   to parse JSON strings, such as in conjunction with
-   CURL for using API responses.
+ * The Milliwatt application's existing behavior is
+   incorrect in that it plays a constant tone, which
+   is not how digital milliwatt test lines actually
+   work.
 
-res_fax_spandsp
-------------------
- * Adds support for spandsp 3.0.0.
+   An option is added so that a proper milliwatt test
+   tone can be provided, including a 1 second silent
+   interval every 10 seconds. However, for compatability
+   reasons, the default behavior remains unchanged.
 
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.8.0 to Asterisk 18.9.0 ------------
-------------------------------------------------------------------------------
+app_morsecode
+------------------
+ * Extends the Morsecode application by adding support for
+   American Morse code and adds a configurable option
+   for the frequency used in off intervals.
 
-ToneScan application
+app_originate
 ------------------
- * A new application, ToneScan, allows for
-   synchronous detection of call progress
-   signals such as dial tone, busy tone,
-   Special Information Tones, and modems.
+ * Codecs can now be specified for dialplan-originated
+   calls, as with call files and the manager action.
+   By default, only the slin codec is now used, instead
+   of all the slin* codecs.
 
 app_playback
 ------------------
@@ -186,6 +440,12 @@ app_playback
 
 app_queue
 ------------------
+ * Reload behavior in app_queue has been changed so
+   queue and agent stats are not reset during full
+   app_queue module reloads. The queue reset stats
+   CLI command may still be used to reset stats while
+   Asterisk is running.
+
  * Add field to save the time value when a member enter a queue.
    Shows this time in seconds using 'queue show' command and the
    field LoginTime for responses for AMI the events.
@@ -194,6 +454,69 @@ app_queue
    extra data field for the information of the time login time for each
    member.
 
+ * added that we set DIALEDPEERNUMBER on the outgoing channels
+   so it is avalible in b(content^extension^line)
+   this add the same behaviour as Dial
+
+ * Load queues and members from Realtime for
+   AMI actions: QueuePause, QueueStatus and QueueSummary,
+   Applications: PauseQueueMember and UnpauseQueueMember.
+
+ * Added a new AMI action: QueueWithdrawCaller
+   This AMI action makes it possible to withdraw a caller from a queue
+   back to the dialplan. The call will be signaled to leave the queue
+   whenever it can, hence, it not guaranteed that the call will leave
+   the queue.
+
+   Optional custom data can be passed in the request, in the WithdrawInfo
+   parameter. If the call successfully withdrawn the queue,
+   it can be retrieved using the QUEUE_WITHDRAW_INFO variable.
+
+   This can be useful for certain uses, such as dispatching the call
+   to a specific extension.
+
+ * The m option now allows an override music on hold
+   class to be specified for the Queue application
+   within the dialplan.
+
+app_queue.c
+------------------
+ * Allow multiple files to be streamed for agent announcement.
+
+app_queues
+------------------
+ * adding support for playing the correct en/et for nordic languages
+
+ * Don't play sound_thanks if there is no leading hold_time message
+   When the only announcement is hold time, and there is no hold time (0 min, 0 sec), asterisk will say "thank you for your patience"
+
+app_read
+------------------
+ * A new option allows the digit '#' to be read literally,
+   rather than used exclusively as the input terminator
+   character.
+
+app_sendtext
+------------------
+ * A ReceiveText application has been added that can be
+   used in conjunction with the SendText application.
+
+app_voicemail
+------------------
+ * Add a new 'S' option to VoiceMail which prevents the instructions
+   (vm-intro) from being played if a busy/unavailable/temporary greeting
+   from the voicemail user is played. This is similar to the existing 's'
+   option except that instructions will still be played if no user
+   greeting is available.
+
+ * added support for Danish syntax, playing the correct plural sound file
+   dependen on where you have 1 or multipe messages
+   based on the existing SE/NO code
+
+ * The r option has been added, which prevents deletion
+   of messages from VoiceMailMain, which can be
+   useful for shared mailboxes.
+
 apps
 ------------------
  * A new option 'mix' is added to the Playback application that 
@@ -201,6 +524,13 @@ apps
    name, if it is like say format it will play with say.conf if not it 
    will play the file name.
 
+ari
+------------------
+ * Expose channel driver's unique id (which is the Call-ID for SIP/PJSIP)
+   to ARI channel resources as 'protocol_id'.
+
+   ASTERISK-30027
+
 ast_coredumper
 ------------------
  * New options:
@@ -225,117 +555,88 @@ ast_coredumper
     If you use 'running' or 'RUNNING' you no longer need to specify
     '--no-default-search' to ignore existing coredumps.
 
-chan_iax2
-------------------
- * Both a secret and an outkey may be specified at dial time,
-   since encryption is possible with RSA authentication.
-
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.7.0 to Asterisk 18.8.0 ------------
-------------------------------------------------------------------------------
-
-MessageSend
+cdr
 ------------------
- * The MessageSend AMI action has been updated to allow the Destination
-   and the To addresses to be provided separately. This brings the
-   MessageSend manager command in line with the capabilities of the
-   MessageSend dialplan application.
+ * A new CDR option, channeldefaultenabled, allows controlling
+   whether CDR is enabled or disabled by default on
+   newly created channels. The default behavior remains
+   unchanged from previous versions of Asterisk (new
+   channels will have CDR enabled, as long as CDR is
+   enabled globally).
 
-func_channel
+chan_dahdi
 ------------------
- * Adds the CHANNEL_EXISTS function to check for the existence
-   of a channel by name or unique ID.
+ * Previously, cadences were appended on dahdi restart,
+   rather than reloaded. This prevented cadences from
+   being updated and maxed out the available cadences
+   if reloaded multiple times. This behavior is fixed
+   so that reloading cadences is idempotent and cadences
+   can actually be reloaded.
 
-func_vmcount
-------------------
- * Multiple mailboxes may now be specified instead of just one.
+ * A POLARITY function is now available that allows
+   getting or setting the polarity on a channel
+   from the dialplan.
 
-logger
+chan_iax2
 ------------------
- * Added the ability to define custom log levels in logger.conf
-   and use them in the Log dialplan application. Also adds a
-   logger show levels CLI command.
+ * ANI2 (OLI) is now transmitted over IAX2 calls
+   as an information element.
 
-res_pjsip_registrar
-------------------
- * Adds new PJSIP AOR option remove_unavailable to either
-   remove unavailable contacts when a REGISTER exceeds
-   max_contacts when remove_existing is disabled, or
-   prioritize unavailable contacts over other existing
-   contacts when remove_existing is enabled.
+ * Both a secret and an outkey may be specified at dial time,
+   since encryption is possible with RSA authentication.
 
-res_pjsip_t38
+chan_pjsip
 ------------------
- * In res_pjsip_sdp_rtp, the bind_rtp_to_media_address option and the
-   fallback use of the transport's bind address solve problems sending
-   media on systems that cannot send ipv4 packets on ipv6 sockets, and
-   certain other situations. This change extends both of these behaviors
-   to UDPTL sessions as well in res_pjsip_t38, to fix fax-specific
-   problems on these systems, introducing a new option
-   endpoint/t38_bind_udptl_to_media_address.
+ * Add function PJSIP_HEADERS() to get list of headers by pattern in the same way as SIP_HEADERS() do.
 
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.6.0 to Asterisk 18.7.0 ------------
-------------------------------------------------------------------------------
+   Add ability to read header by pattern using PJSIP_HEADER().
 
-Channel-agnostic MF support
-------------------
- * A SendMF application and PlayMF manager
-   application are now included to send
-   arbitrary standard R1 MF tones on the
-   current channel or another specified channel.
+ * added global config option "allow_sending_180_after_183"
 
-app_milliwatt
-------------------
- * The Milliwatt application's existing behavior is
-   incorrect in that it plays a constant tone, which
-   is not how digital milliwatt test lines actually
-   work.
+   Allow Asterisk to send 180 Ringing to an endpoint
+   after 183 Session Progress has been send.
+   If disabled Asterisk will instead send only a
+   183 Session Progress to the endpoint.
 
-   An option is added so that a proper milliwatt test
-   tone can be provided, including a 1 second silent
-   interval every 10 seconds. However, for compatability
-   reasons, the default behavior remains unchanged.
+ * Hook flash events can now be sent on a PJSIP channel
+   if requested to do so.
 
-app_morsecode
+chan_sip
 ------------------
- * Extends the Morsecode application by adding support for
-   American Morse code and adds a configurable option
-   for the frequency used in off intervals.
+ * Session timers get removed on UPDATE
+   Fix if Asterisk receives a SIP REFER with Session-Timers UAC
+   that Asterisk maintains Session-Timers when sending UPDATE request
 
-app_originate
+chan_sip.c
 ------------------
- * Codecs can now be specified for dialplan-originated
-   calls, as with call files and the manager action.
-   By default, only the slin codec is now used, instead
-   of all the slin* codecs.
+ * resolve issue with pickup on device that uses "183" and not "180"
 
-app_queue
+channel_internal_api
 ------------------
- * Reload behavior in app_queue has been changed so
-   queue and agent stats are not reset during full
-   app_queue module reloads. The queue reset stats
-   CLI command may still be used to reset stats while
-   Asterisk is running.
+ * CHANNEL(lastcontext) and CHANNEL(lastexten)
+   are now available for use in the dialplan.
 
-app_read
+cli
 ------------------
- * A new option allows the digit '#' to be read literally,
-   rather than used exclusively as the input terminator
-   character.
+ * The "module refresh" command has been added,
+   which allows unloading and then loading a
+   module with a single command.
 
-app_voicemail
+ * A new CLI command 'dialplan eval function' has been
+   added which allows users to test the behavior of
+   dialplan function calls directly from the CLI.
+
+func_channel
 ------------------
- * Add a new 'S' option to VoiceMail which prevents the instructions
-   (vm-intro) from being played if a busy/unavailable/temporary greeting
-   from the voicemail user is played. This is similar to the existing 's'
-   option except that instructions will still be played if no user
-   greeting is available.
+ * Adds the CHANNEL_EXISTS function to check for the existence
+   of a channel by name or unique ID.
 
-chan_iax2
+func_db
 ------------------
- * ANI2 (OLI) is now transmitted over IAX2 calls
-   as an information element.
+ * The function DB_KEYCOUNT has been added, which
+   returns the cardinality of the keys at a specified
+   prefix in AstDB, i.e. the number of keys at a
+   given prefix.
 
 func_env.c
 ------------------
@@ -343,11 +644,30 @@ func_env.c
    included which allow users to obtain the directory
    or the base filename of any file.
 
+func_evalexten
+------------------
+ * This adds the EVAL_EXTEN function which may be
+   used to evaluate data at dialplan extensions.
+
 func_framedrop
 ------------------
  * New function to selectively drop specified frames
    in either direction on a channel.
 
+func_json
+------------------
+ * The JSON_DECODE dialplan function can now be used
+   to parse JSON strings, such as in conjunction with
+   CURL for using API responses.
+
+func_odbc
+------------------
+ * A SQL_ESC_BACKSLASHES dialplan function has been added which
+   escapes backslashes. Usage of this is dependent on whether the
+   database in use can use backslashes to escape ticks or not. If
+   it can, then usage of this prevents a broken SQL query depending
+   on how the SQL query is constructed.
+
 func_scramble
 ------------------
  * Adds an audio scrambler function that may be used to
@@ -362,6 +682,43 @@ func_strings
    dial strings, such as adding pauses between digits
    for a string of digits that are sent to another channel.
 
+func_vmcount
+------------------
+ * Multiple mailboxes may now be specified instead of just one.
+
+logger
+------------------
+ * Added the ability to define custom log levels in logger.conf
+   and use them in the Log dialplan application. Also adds a
+   logger show levels CLI command.
+
+res_agi
+------------------
+ * Agi command 'exec' can now be enabled
+   to evaluate dialplan functions and variables
+   by setting the variable AGIEXECFULL to yes.
+
+res_cliexec
+------------------
+ * A new CLI command, dialplan exec application, has
+   been added which allows dialplan applications to be
+   executed at the CLI, useful for some quick testing
+   without needing to write dialplan.
+
+res_fax_spandsp
+------------------
+ * Adds support for spandsp 3.0.0.
+
+res_geolocation
+------------------
+ * Added res_geolocation which creates the core capabilities
+   to manipulate Geolocation information on SIP INVITEs.
+
+res_parking
+------------------
+ * An m option to Park and ParkAndAnnounce now allows
+   specifying a music on hold class override.
+
 res_pjproject
 ------------------
  * In pjproject.conf you can now map pjproject log levels
@@ -371,6 +728,62 @@ res_pjproject
    to TRACE.  Previously 3, 4, 5, and 6 were all mapped to
    DEBUG.
 
+res_pjsip
+------------------
+ * A new transport option 'allow_wildcard_certs' has been added that when it
+   and 'verify_server' are both set to 'yes', enables verification against
+   wildcards, i.e. '*.' in certs for common, and subject alt names of type DNS
+   for TLS transport types. Names must start with the wildcard. Partial wildcards,
+   e.g. 'f*.example.com' and 'foo.*.com' are not allowed. As well, names only
+   match against a single level meaning '*.example.com' matches 'foo.example.com',
+   but not 'foo.bar.example.com'.
+
+res_pjsip_geolocation
+------------------
+ * Added res_pjsip_geolocation which gives chan_pjsip
+   the ability to use the core geolocation capabilities.
+
+res_pjsip_header_funcs
+------------------
+ * Add function PJSIP_RESPONSE_HEADERS() to get list of header names from 200 response, in the same way as PJSIP_HEADERS() from the request.
+
+   Add function PJSIP_RESPONSE_HEADER() to read header from 200 response, in the same way as PJSIP_HEADER() from the request.
+
+res_pjsip_pubsub
+------------------
+ * A new resource_list option, resource_display_name, indicates
+   whether display name of resource or the resource name being
+   provided for RLS entries.
+   If this option is enabled, the Display Name will be provided.
+   This option is disabled by default to remain the previous behavior.
+   If the 'event' set to 'presence' or 'dialog' the non-empty HINT name
+   will be set as the Display Name.
+   The 'message-summary' is not supported yet.
+
+ * The Resource List Subscriptions (RLS) is dynamic now.
+   The asterisk now updates current subscriptions to reflect the changes
+   to the list on subscription refresh. If list items are added,
+   removed, updated or do not exist anymore, the asterisk regenerates
+   the resource list.
+
+res_pjsip_registrar
+------------------
+ * Adds new PJSIP AOR option remove_unavailable to either
+   remove unavailable contacts when a REGISTER exceeds
+   max_contacts when remove_existing is disabled, or
+   prioritize unavailable contacts over other existing
+   contacts when remove_existing is enabled.
+
+res_pjsip_t38
+------------------
+ * In res_pjsip_sdp_rtp, the bind_rtp_to_media_address option and the
+   fallback use of the transport's bind address solve problems sending
+   media on systems that cannot send ipv4 packets on ipv6 sockets, and
+   certain other situations. This change extends both of these behaviors
+   to UDPTL sessions as well in res_pjsip_t38, to fix fax-specific
+   problems on these systems, introducing a new option
+   endpoint/t38_bind_udptl_to_media_address.
+
 res_rtp_asterisk
 ------------------
  * When the address of the STUN server (stunaddr) is a name resolved via DNS, the
@@ -392,75 +805,52 @@ say.c
 
    Additionally adds SayMoney and SayOrdinal applications.
 
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.5.0 to Asterisk 18.6.0 ------------
-------------------------------------------------------------------------------
-
-Handle non-standard Meter metric type safely
-------------------
- * A meter_support flag has been introduced that defaults to true to maintain current behaviour.
-   If disabled, a counter metric type will be used instead wherever a meter metric type was used,
-   the counter will have a "_meter" suffix appended to the metric name.
-
-app_dtmfstore
-------------------
- * New application which collects digits
-   dialed and stores them into
-   a specified variable.
-
-app_queue.c
-------------------
- * Allow multiple files to be streamed for agent announcement.
-
-chan_pjsip
+stasis_channels
 ------------------
- * Add function PJSIP_HEADERS() to get list of headers by pattern in the same way as SIP_HEADERS() do.
+ * Expose channel driver's unique id (which is the Call-ID for SIP/PJSIP)
+   to ARI channel resources as 'protocol_id'.
 
-   Add ability to read header by pattern using PJSIP_HEADER().
+   ASTERISK-30027
 
 ------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.5.0 to Asterisk 18.5.1 ------------
+--- Functionality changes from Asterisk 18.0.0 to Asterisk 19.0.0 ------------
 ------------------------------------------------------------------------------
 
-New Reload application
+AMI Flash event
 ------------------
- * Adds an application to reload modules
+ * Hook flash events are now exposed as AMI events.
 
-PlaybackFinished has a new error state
+Add variable support to Originate
 ------------------
- * The PlaybackFinished event now has a new state "failed"
-   that is used when the sound file was not played due to an error.
-   Before the state on PlaybackFinished was always "done".
-
-   In case of multiple sound files to be played,
-   the PlaybackFinished is sent only once in the end of the list,
-   even in case of error.
+ * The Originate application now allows
+   variables to be set on the new channel
+   through a new option.
 
-WaitForCondition application
+Core
 ------------------
- * This application provides a way to halt
-   dialplan execution until a provided
-   condition evaluates to true.
+ * Added debug logging categories that allow a user to output debug information
+   based on a specified category. This lets the user limit, and filter debug
+   output to data relevant to a particular context, or topic. For instance the
+   following categories are now available for debug logging purposes:
 
-app_dial announcement option
-------------------
- * The A option for Dial now supports
-   playing audio to the caller as well
-   as the called party.
+     dtls, dtls_packet, ice, rtcp, rtcp_packet, rtp, rtp_packet, stun, stun_packet
+
+   These debug categories can be enable/disable via an Asterisk CLI command:
+
+     core set debug category <category>[:<sublevel>] [category[:<sublevel] ...]
+     core set debug category off [<category> [<category>] ...]
 
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.4.0 to Asterisk 18.5.0 ------------
-------------------------------------------------------------------------------
+   If no sub-level is associated all debug statements for a given category are
+   output. If a sub-level is given then only those statements assigned a value
+   at or below the associated sub-level are output.
 
-AMI Flash event
-------------------
- * Hook flash events are now exposed as AMI events.
+ * The location where the media cache stores its temporary files
+   is no longer hardcoded to /tmp but can now be configured separately
+   via the astcachedir config variable in asterisk.conf.
 
-Add variable support to Originate
-------------------
- * The Originate application now allows
-   variables to be set on the new channel
-   through a new option.
+   The default location for astcachedir is now /var/cache/asterisk
+   instead of /tmp, please make sure to manually cleanup and/or
+   migrate the temporary files in /tmp after upgrading.
 
 MessageSend
 ------------------
@@ -485,6 +875,34 @@ New ConfKick application
    a specific channel, all users, or all non-admin
    users to be kicked from a conference bridge.
 
+New Reload application
+------------------
+ * Adds an application to reload modules
+
+PlaybackFinished has a new error state
+------------------
+ * The PlaybackFinished event now has a new state "failed"
+   that is used when the sound file was not played due to an error.
+   Before the state on PlaybackFinished was always "done".
+
+   In case of multiple sound files to be played,
+   the PlaybackFinished is sent only once in the end of the list,
+   even in case of error.
+
+WaitForCondition application
+------------------
+ * This application provides a way to halt
+   dialplan execution until a provided
+   condition evaluates to true.
+
+app_confbridge
+------------------
+ * app_confbridge now has the ability to force the estimated bitrate on an SFU
+   bridge.  To use it, set a bridge profile's remb_behavior to "force" and
+   set remb_estimated_bitrate to a rate in bits per second.  The
+   remb_estimated_bitrate parameter is ignored if remb_behavior is something
+   other than "force".
+
 app_confbridge answer supervision control
 ------------------
  * app_confbridge now provides a user option to prevent
@@ -492,55 +910,88 @@ app_confbridge answer supervision control
    answered yet. To use it, set a user profile's
    answer_channel option to no.
 
+app_dial announcement option
+------------------
+ * The A option for Dial now supports
+   playing audio to the caller as well
+   as the called party.
+
+app_mixmonitor
+------------------
+ * app_mixmonitor now sends manager events MixMonitorStart, MixMonitorStop and
+   MixMonitorMute when the channel monitoring is started, stopped and muted (or
+   unmuted) respectively.
+
 app_voicemail
 ------------------
+ * The VoiceMail application can now be configured to send greetings and
+   instructions via early media and only answering the channel when it is
+   time for the caller to record their message. This behavior can be
+   activated by passing the new 'e' option to VoiceMail.
+
  * You can now customize the "beep" tone or omit it entirely.
 
-func_math: Three new dialplan functions
+chan_iax2
 ------------------
- * Introduce three new functions, MIN, MAX, and ABS, which can be used to
-   obtain the minimum or maximum of up to two integers or absolute value.
+ * You can now specify a default "auth" method in the
+   [general] section of iax.conf
 
-func_volume now can be read
+chan_pjsip
 ------------------
- * The VOLUME function can now also be used
-   to read existing values previously set.
+ * The PJSIP_SEND_SESSION_REFRESH dialplan function now issues a warning, and
+   returns unsuccessful if it's used on a channel prior to answering.
 
-res_pjsip
+chan_pjsip, app_transfer
 ------------------
- * PJSIP support of registrations of endpoints in multidomain
-   scenarios, where the endpoint contains the domain info
-   in pjsip.conf.
+ * Added TRANSFERSTATUSPROTOCOL variable.  When transfer is performed,
+   transfers can pass a protocol specific error code.
+   Example, in SIP 3xx-6xx represent any SIP specific error received when
+   performing a REFER.
 
-res_pjsip_dialog_info_body_generator
+func_math: Three new dialplan functions
 ------------------
- * PJSIP now supports RFC 4235 Section 4.1.6 dialog-info+xml local and
-   remote elements by iterating through ringing channels and inserting
-   that info into NOTIFY packet sent to the endpoint.
+ * Introduce three new functions, MIN, MAX, and ABS, which can be used to
+   obtain the minimum or maximum of up to two integers or absolute value.
 
-res_pjsip_messaging
+func_odbc
 ------------------
- * Implemented the new "to" parameter of the MessageSend()
-   dialplan application.  This allows a user to specify
-   a complete SIP "To" header separate from the Request URI.
-   We now also accept a destination in the same format
-   as Dial()...  PJSIP/number@endpoint
+ * Introduce an ARGC variable for func_odbc functions, along with a minargs
+   per-function configuration option.
 
-res_rtp_asterisk
-------------------
- * By default Asterisk reports the PJSIP version in all
-   STUN packets it sends.
+   minargs enables enforcing of minimum count of arguments to pass to
+   func_odbc, so if you're unconditionally using ARG1 through ARG4 then
+   this should be set to 4.  func_odbc will generate an error in this case,
+   so for example
 
-   This behaviour may not be desired in a production
-   environment and can now be disabled by setting the
-   stun_software_attribute option to 'no' in rtp.conf.
+   [FOO]
+   minargs = 4
 
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.3.0 to Asterisk 18.4.0 ------------
-------------------------------------------------------------------------------
+   and ODBC_FOO(a,b,c) in dialplan will now error out instead of using a
+   potentially leaked ARG4 from Gosub().
+
+   ARGC is needed if you're using optional argument, to verify whether or
+   not an argument has been passed, else it's possible to use a leaked ARGn
+   from Gosub (app_stack).  So now you can safely do
+   ${IF($[${ARGC}>3]?${ARGV}:default value)} kind of thing.
+
+func_volume now can be read
+------------------
+ * The VOLUME function can now also be used
+   to read existing values previously set.
 
 logger
 ------------------
+ * Added a new log formatter called "plain" that always prints
+   file, function and line number if available (even for verbose
+   messages) and never prints color control characters.  Most
+   suitable for file output but can be used for other channels
+   as well.
+
+   You use it in logger.conf like so:
+   debug => [plain]debug
+   console => [plain]error,warning,debug,notice,pjsip_history
+   messages => [plain]warning,error,verbose
+
  * The dateformat option in logger.conf will now control the remote
    console (asterisk -r -T) timestamp format.  Previously, dateformat only
    controlled the formatting of the timestamp going to log files and the
@@ -596,48 +1047,32 @@ res_pjsip
    handling OPTIONS requests by setting the allow_unauthenticated_options
    configuration property to 'yes.'
 
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.2.2 to Asterisk 18.3.0 ------------
-------------------------------------------------------------------------------
-
-app_mixmonitor
-------------------
- * app_mixmonitor now sends manager events MixMonitorStart, MixMonitorStop and
-   MixMonitorMute when the channel monitoring is started, stopped and muted (or
-   unmuted) respectively.
+ * PJSIP support of registrations of endpoints in multidomain
+   scenarios, where the endpoint contains the domain info
+   in pjsip.conf.
 
-chan_iax2
+res_pjsip_dialog_info_body_generator
 ------------------
- * You can now specify a default "auth" method in the
-   [general] section of iax.conf
+ * PJSIP now supports RFC 4235 Section 4.1.6 dialog-info+xml local and
+   remote elements by iterating through ringing channels and inserting
+   that info into NOTIFY packet sent to the endpoint.
 
-chan_pjsip, app_transfer
+res_pjsip_messaging
 ------------------
- * Added TRANSFERSTATUSPROTOCOL variable.  When transfer is performed,
-   transfers can pass a protocol specific error code.
-   Example, in SIP 3xx-6xx represent any SIP specific error received when
-   performing a REFER.
+ * Implemented the new "to" parameter of the MessageSend()
+   dialplan application.  This allows a user to specify
+   a complete SIP "To" header separate from the Request URI.
+   We now also accept a destination in the same format
+   as Dial()...  PJSIP/number@endpoint
 
-func_odbc
+res_rtp_asterisk
 ------------------
- * Introduce an ARGC variable for func_odbc functions, along with a minargs
-   per-function configuration option.
-
-   minargs enables enforcing of minimum count of arguments to pass to
-   func_odbc, so if you're unconditionally using ARG1 through ARG4 then
-   this should be set to 4.  func_odbc will generate an error in this case,
-   so for example
-
-   [FOO]
-   minargs = 4
-
-   and ODBC_FOO(a,b,c) in dialplan will now error out instead of using a
-   potentially leaked ARG4 from Gosub().
+ * By default Asterisk reports the PJSIP version in all
+   STUN packets it sends.
 
-   ARGC is needed if you're using optional argument, to verify whether or
-   not an argument has been passed, else it's possible to use a leaked ARGn
-   from Gosub (app_stack).  So now you can safely do
-   ${IF($[${ARGC}>3]?${ARGV}:default value)} kind of thing.
+   This behaviour may not be desired in a production
+   environment and can now be disabled by setting the
+   stun_software_attribute option to 'no' in rtp.conf.
 
 res_srtp
 ------------------
@@ -649,76 +1084,6 @@ res_srtp
    no, or one way, audio and Asterisk error messages like
    "replay check failed".
 
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.1.0 to Asterisk 18.2.0 ------------
-------------------------------------------------------------------------------
-
-Core
-------------------
- * The location where the media cache stores its temporary files
-   is no longer hardcoded to /tmp but can now be configured separately
-   via the astcachedir config variable in asterisk.conf. To retain
-   backwards compatibility, the default location remains /tmp.
-
-app_voicemail
-------------------
- * The VoiceMail application can now be configured to send greetings and
-   instructions via early media and only answering the channel when it is
-   time for the caller to record their message. This behavior can be
-   activated by passing the new 'e' option to VoiceMail.
-
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.0.0 to Asterisk 18.1.0 ------------
-------------------------------------------------------------------------------
-
-Core
-------------------
- * Added debug logging categories that allow a user to output debug information
-   based on a specified category. This lets the user limit, and filter debug
-   output to data relevant to a particular context, or topic. For instance the
-   following categories are now available for debug logging purposes:
-
-     dtls, dtls_packet, ice, rtcp, rtcp_packet, rtp, rtp_packet, stun, stun_packet
-
-   These debug categories can be enable/disable via an Asterisk CLI command:
-
-     core set debug category <category>[:<sublevel>] [category[:<sublevel] ...]
-     core set debug category off [<category> [<category>] ...]
-
-   If no sub-level is associated all debug statements for a given category are
-   output. If a sub-level is given then only those statements assigned a value
-   at or below the associated sub-level are output.
-
-app_confbridge
-------------------
- * app_confbridge now has the ability to force the estimated bitrate on an SFU
-   bridge.  To use it, set a bridge profile's remb_behavior to "force" and
-   set remb_estimated_bitrate to a rate in bits per second.  The
-   remb_estimated_bitrate parameter is ignored if remb_behavior is something
-   other than "force".
-
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 17.0.0 to Asterisk 18.0.0 ------------
-------------------------------------------------------------------------------
-
-chan_pjsip
-------------------
- * The PJSIP_SEND_SESSION_REFRESH dialplan function now issues a warning, and
-   returns unsuccessful if it's used on a channel prior to answering.
-
-logger
-------------------
- * Added a new log formatter called "plain" that always prints
-   file, function and line number if available (even for verbose
-   messages) and never prints color control characters.  Most
-   suitable for file output but can be used for other channels
-   as well.
-
-   You use it in logger.conf like so:
-   debug => [plain]debug
-   console => [plain]error,warning,debug,notice,pjsip_history
-   messages => [plain]warning,error,verbose
-
 ------------------------------------------------------------------------------
 --- New functionality introduced in Asterisk 18.0.0 --------------------------
 ------------------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLogs/historical/ChangeLog
similarity index 94%
rename from ChangeLog
rename to ChangeLogs/historical/ChangeLog
index 96e7d340d774d6497364eb4d12ac862ae24daaba..b3a71c99dfb0e69ac03f40d7c8ad83fbfecdca0f 100644
--- a/ChangeLog
+++ b/ChangeLogs/historical/ChangeLog
@@ -1,7841 +1,14072 @@
-2022-04-14 21:53 +0000  Asterisk Development Team <asteriskteam@digium.com>
+2023-04-03 15:49 +0000  Asterisk Development Team <asteriskteam@digium.com>
 
-	* asterisk 18.11.2 Released.
+	* asterisk 20.2.1 Released.
 
-2022-04-14 16:37 +0000 [94eac3c13c]  Asterisk Development Team <asteriskteam@digium.com>
+2023-03-29 13:49 +0000 [f8dfbaf225]  Mike Bradeen <mbradeen@sangoma.com>
 
-	* Doing a fresh summary
+	* res_pjsip_pubsub: subscription cleanup changes
 
-2022-04-14 16:00 +0000 [3ca29c9554]  Asterisk Development Team <asteriskteam@digium.com>
+	  There are two main parts of the change associated with this
+	  commit. These are driven by the change in call order of
+	  pubsub_on_rx_refresh and pubsub_on_evsub_state by pjproject
+	  when an in-dialog SUBSCRIBE is received.
 
-	* Update for 18.11.2
+	  First, the previous behavior was for pjproject to call
+	  pubsub_on_rx_refresh before calling pubsub_on_evsub_state
+	  when an in-dialog SUBSCRIBE was received that changes the
+	  subscription state.
 
-2022-04-14 15:47 +0000 [1422d098e7]  Asterisk Development Team <asteriskteam@digium.com>
+	  If that change was a termination due to a re-SUBSCRIBE with
+	  an expires of 0, we used to use the call to pubsub_on_rx_refresh
+	  to set the substate of the evsub to TERMINATE_PENDING before
+	  pjproject could call pubsub_on_evsub_state.
 
-	* Update CHANGES and UPGRADE.txt for 18.11.2
-2022-02-28 11:19 +0000 [353142a2b4]  Ben Ford <bford@digium.com>
+	  This substate let pubsub_on_evsub_state know that the
+	  subscription TERMINATED event could be ignored as there was
+	  still a subsequent NOTIFY that needed to be generated and
+	  another call to pubsub_on_evsub_state to come with it.
 
-	* AST-2022-002 - res_stir_shaken/curl: Add ACL checks for Identity header.
+	  That NOTIFY was sent via serialized_pubsub_on_refresh_timeout
+	  which would see the TERMINATE_PENDING state and transition it
+	  to TERMINATE_IN_PROGRESS before triggering another call to
+	  pubsub_on_evsub_state (which now would clean up the evsub.)
 
-	  Adds a new configuration option, stir_shaken_profile, in pjsip.conf that
-	  can be specified on a per endpoint basis. This option will reference a
-	  stir_shaken_profile that can be configured in stir_shaken.conf. The type
-	  of this option must be 'profile'. The stir_shaken option can be
-	  specified on this object with the same values as before (attest, verify,
-	  on), but it cannot be off since having the profile itself implies wanting
-	  STIR/SHAKEN support. You can also specify an ACL from acl.conf (along
-	  with permit and deny lines in the object itself) that will be used to
-	  limit what interfaces Asterisk will attempt to retrieve information from
-	  when reading the Identity header.
+	  The new pjproject behavior is to call pubsub_on_evsub_state
+	  before pubsub_on_rx_refresh. This means we no longer can set
+	  the state to TERMINATE_PENDING to tell pubsub_on_evsub_state
+	  that it can ignore the first TERMINATED event.
 
-	  ASTERISK-29476
+	  To handle this, we now look directly at the event type,
+	  method type and the expires value to determine whether we
+	  want to ignore the event or use it to trigger the evsub
+	  cleanup.
 
-	  Change-Id: I87fa61f78a9ea0cd42530691a30da3c781842406
+	  Second, pjproject now expects the NOTIFY to actually be sent
+	  during pubsub_on_rx_refresh and avoids the protocol violation
+	  inherent in sending a NOTIFY before the SUBSCRIBE is
+	  acknowledged by caching the sent NOTIFY then sending it
+	  after responding to the SUBSCRIBE.
 
-2022-01-07 08:50 +0000 [1fdb1a6edf]  Ben Ford <bford@digium.com>
+	  This requires we send the NOTIFY using the non-serialized
+	  pubsub_on_refresh_timeout directly and let pjproject handle
+	  the protocol violation.
 
-	* AST-2022-001 - res_stir_shaken/curl: Limit file size and check start.
+	  ASTERISK-30469
 
-	  Put checks in place to limit how much we will actually download, as well
-	  as a check for the data we receive at the start to ensure it begins with
-	  what we would expect a certificate to begin with.
+	  Change-Id: I05c1d91a44fe28244ae93faa4a2268a3332b5fd7
 
-	  ASTERISK-29872
+2023-03-19 16:30 +0000 [6e50550d28]  Sean Bright <sean@seanbright.com>
 
-	  Change-Id: Ifd3c6b8bd52b8b6192a04166ccce4fc8a8000b46
+	* Revert "pbx_ael: Global variables are not expanded."
 
-2022-02-10 06:02 +0000 [88522c22aa]  Joshua C. Colp <jcolp@sangoma.com>
+	  This reverts commit 56051d1ac5115ff8c55b920fc441613c487fb512.
 
-	* func_odbc: Add SQL_ESC_BACKSLASHES dialplan function.
+	  Reason for revert: Behavior change that breaks existing dialplan.
 
-	  Some databases depending on their configuration using backslashes
-	  for escaping. When combined with the use of ' this can result in
-	  a broken func_odbc query.
+	  ASTERISK-30472 #close
 
-	  This change adds a SQL_ESC_BACKSLASHES dialplan function which can
-	  be used to escape the backslashes.
+	  Change-Id: I83bed3b800d36228a04ded0a6164b795f7f16bd6
 
-	  This is done as a dialplan function instead of being always done
-	  as some databases do not require this, and always doing it would
-	  result in incorrect data being put into the database.
+2023-03-09 17:17 +0000  Asterisk Development Team <asteriskteam@digium.com>
 
-	  ASTERISK-29838
+	* asterisk 20.2.0 Released.
 
-	  Change-Id: I152bf34899b96ddb09cca3e767254d8d78f0c83d
+2023-03-02 16:45 +0000  Asterisk Development Team <asteriskteam@digium.com>
 
-2022-03-29 21:46 +0000  Asterisk Development Team <asteriskteam@digium.com>
+	* asterisk 20.2.0-rc1 Released.
 
-	* asterisk 18.11.1 Released.
+2023-03-02 10:37 +0000 [93813c9dca]  Asterisk Development Team <asteriskteam@digium.com>
 
-2022-03-25 09:33 +0000 [663b565647]  George Joseph <gjoseph@digium.com>
+	* Update CHANGES and UPGRADE.txt for 20.2.0
+2023-02-16 10:05 +0000 [ceda5a9859]  George Joseph <gjoseph@sangoma.com>
 
-	* make_xml_documentation: Remove usage of get_sourceable_makeopts
+	* res_pjsip: Replace invalid UTF-8 sequences in callerid name
 
-	  get_sourceable_makeopts wasn't handling variables with embedded
-	  double quotes in them very well.  One example was the DOWNLOAD
-	  variable when curl was being used instead of wget.  Rather than
-	  trying to fix get_sourceable_makeopts, it's just been removed.
+	  * Added a new function ast_utf8_replace_invalid_chars() to
+	    utf8.c that copies a string replacing any invalid UTF-8
+	    sequences with the Unicode specified U+FFFD replacement
+	    character.  For example:  "abc\xffdef" becomes "abc\uFFFDdef".
+	    Any UTF-8 compliant implementation will show that character
+	    as a � character.
 
-	  ASTERISK-29986
-	  Reported by: Stefan Ruijsenaars
+	  * Updated res_pjsip:set_id_from_hdr() to use
+	    ast_utf8_replace_invalid_chars and print a warning if any
+	    invalid sequences were found during the copy.
 
-	  Change-Id: Idf2a90902228c2558daa5be7a4f8327556099cd2
+	  * Updated stasis_channels:ast_channel_publish_varset to use
+	    ast_utf8_replace_invalid_chars and print a warning if any
+	    invalid sequences were found during the copy.
 
-2022-03-25 14:00 +0000 [99fa792482]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-27830
 
-	* Makefile:  Disable XML doc validation
+	  Change-Id: I4ffbdb19c80bf0efc675d40078a3ca4f85c567d8
 
-	  make_xml_documentation was being called with the --validate
-	  flag set when it shouldn't have been.  This was causing
-	  build failures if neither xmllint nor xmlstarlet were installed.
-	  The correct behavior is to simply print a message that either
-	  one of those tools should be installed for validation and
-	  continue with the build.
+2023-02-27 18:35 +0000 [e5c5cd6e25]  Sean Bright <sean@seanbright.com>
 
-	  ASTERISK-29988
+	* test.c: Avoid passing -1 to FD_* family of functions.
 
-	  Change-Id: Idc6c44114e7dd3fadae183a4e22f4fdba0b8a645
+	  This avoids buffer overflow errors when running tests that capture
+	  output from child processes.
 
-2022-03-24 13:12 +0000  Asterisk Development Team <asteriskteam@digium.com>
+	  This also corrects a copypasta in an off-nominal error message.
 
-	* asterisk 18.11.0 Released.
+	  Change-Id: Ib482847a3515364f14c7e7a0c0a4213851ddb10d
 
-2022-03-17 15:40 +0000  Asterisk Development Team <asteriskteam@digium.com>
+2022-12-14 10:00 +0000 [ede67a99be]  Naveen Albert <asterisk@phreaknet.org>
 
-	* asterisk 18.11.0-rc1 Released.
+	* chan_iax2: Fix jitterbuffer regression prior to receiving audio.
 
-2022-03-17 10:23 +0000 [5dd61a6e16]  Asterisk Development Team <asteriskteam@digium.com>
+	  ASTERISK_29392 (a security fix) introduced a regression by
+	  not processing frames when we don't have an audio format.
 
-	* Update CHANGES and UPGRADE.txt for 18.11.0
-2022-03-17 09:19 +0000 [81de525c6e]  Ben Ford <bford@digium.com>
+	  Currently, chan_iax2 only calls jb_get to read frames from
+	  the jitterbuffer when the voiceformat has been set on the pvt.
+	  However, this only happens when we receive a voice frame, which
+	  means that prior to receiving voice frames, other types of frames
+	  get stalled completely in the jitterbuffer.
 
-	* AMI: Bump version for 18.11.0.
+	  To fix this, we now fallback to using the format negotiated during
+	  call setup until we've actually received a voice frame with a format.
+	  This ensures we're always able to read from the jitterbuffer.
 
-	  Change-Id: Ic15cfca9e68efd06a1b12ab2335d52a5890e7170
+	  ASTERISK-30354 #close
+	  ASTERISK-30162 #close
 
-2022-02-18 03:19 +0000 [5b653b8a7b]  Boris P. Korzun <drtr0jan@yandex.ru>
+	  Change-Id: Ie4fd1e8e088a145ad89e0427c2100a530e964fe9
 
-	* res_config_pgsql: Add text-type column check in require_pgsql()
+2023-02-27 15:35 +0000 [827222d607]  Sean Bright <sean@seanbright.com>
 
-	  Omit "unsupported column type 'text'" warning in logs while
-	  using text-type column in the PgSQL backend.
+	* test_crypto.c: Fix getcwd(…) build error.
 
-	  ASTERISK-29924 #close
+	  `getcwd(…)` is decorated with the `warn_unused_result` attribute and
+	  therefore needs its return value checked.
 
-	  Change-Id: I48061a7d469426859670db07f1ed8af1eb814712
+	  Change-Id: Idcccb20a0abf293202c28633d0e9ee0f6a9dbe93
 
-2022-02-09 04:28 +0000 [3959d20ba5]  Kfir Itzhak <mastertheknife@gmail.com>
+2023-02-11 06:58 +0000 [200dc7d0e8]  Nick French <nickfrench@gmail.com>
 
-	* app_queue: Add QueueWithdrawCaller AMI action
+	* pjproject_bundled: Fix cross-compilation with SSL libs.
 
-	  This adds a new AMI action called QueueWithdrawCaller.
-	  This AMI action makes it possible to withdraw a caller from a queue,
-	  in a safe and a generic manner.
-	  This can be useful for retrieving a specific call and
-	  dispatching it to a specific extension.
-	  It works by signaling the caller to exit the queue application
-	  whenever it can. Therefore, it is not guaranteed
-	  that the call will leave the queue.
+	  Asterisk makefiles auto-detect SSL library availability,
+	  then they assume that pjproject makefiles will also autodetect
+	  an SSL library at the same time, so they do not pass on the
+	  autodetection result to pjproject.
 
-	  ASTERISK-29909 #close
+	  This normally works, except the pjproject makefiles disables
+	  autodetection when cross-compiling.
 
-	  Change-Id: Ic15aa238e23b2884abdcaadff2fda7679e29b7ec
+	  Fix by explicitly configuring pjproject to use SSL if we
+	  have been told to use it or it was autodetected
 
-2022-02-08 16:58 +0000 [8666455bd8]  Alexei Gradinari <alex2grad@gmail.com>
+	  ASTERISK-30424 #close
 
-	* res_pjsip_pubsub: update RLS to reflect the changes to the lists
+	  Change-Id: I8fe2999ea46710e21d1d55a1bed92769c6ebded9
 
-	  This patch makes the Resource List Subscriptions (RLS) dynamic.
-	  The asterisk updates the current subscriptions to reflect the changes
-	  to the list on the subscriptions refresh. If list items are added,
-	  removed, updated or do not exist anymore, the asterisk regenerates
-	  the resource list.
+2023-01-30 17:14 +0000 [5c11d7adea]  Mike Bradeen <mbradeen@sangoma.com>
 
-	  ASTERISK-29906 #close
+	* app_read: Add an option to return terminator on empty digits.
 
-	  Change-Id: Icee8c00459a7aaa43c643d77ce6f16fb7ab037d3
+	  Adds 'e' option to allow Read() to return the terminator as the
+	  dialed digits in the case where only the terminator is entered.
 
-2022-03-03 16:44 +0000 [9e74563a50]  Kevin Harwell <kharwell@sangoma.com>
+	  ie; if "#" is entered, return "#" if the 'e' option is set and ""
+	  if it is not.
 
-	* AST-2022-006: pjproject - unconstrained malformed multipart SIP message
+	  ASTERISK-30411
 
-	  ASTERISK-29945 #close
+	  Change-Id: I49f3221824330a193a20c660f99da0f1fc2cbbc5
 
-	  Change-Id: Ic58957afc453195d53c2bd25c905df3d91d1abe6
+2023-01-07 23:04 +0000 [5b0e3444c3]  cmaj <chris@penguinpbx.com>
 
-2022-03-03 16:42 +0000 [742d265ff5]  Kevin Harwell <kharwell@sangoma.com>
+	* res_phoneprov.c: Multihomed SERVER cache prevention
 
-	* AST-2022-005: pjproject - undefined behavior after freeing a dialog set
+	  Phones moving between subnets on multi-homed server have their
+	  initially connected interface IP cached in the SERVER variable,
+	  even when it is not specified in the configuration files. This
+	  prevents phones from obtaining the correct SERVER variable value
+	  when they move to another subnet.
 
-	  ASTERISK-29945 #close
+	  ASTERISK-30388 #close
+	  Reported-by: cmaj
 
-	  Change-Id: Ia8ce6d82b115c82c1138747c72a0adcaa42b718c
+	  Change-Id: I1d18987a9d58e85556b4c4a6814ce7006524cc92
 
-2022-03-03 16:41 +0000 [fc160abb67]  Kevin Harwell <kharwell@sangoma.com>
+2023-01-27 14:23 +0000 [2308afed8e]  Mike Bradeen <mbradeen@sangoma.com>
 
-	* AST-2022-004: pjproject - possible integer underflow on STUN message
+	* app_directory: Add a 'skip call' option.
 
-	  ASTERISK-29945 #close
+	  Adds 's' option to skip calling the extension and instead set the
+	  extension as DIRECTORY_EXTEN channel variable.
 
-	  Change-Id: I721cd254e4f8aa6d3a97a37529cca53519694c54
+	  ASTERISK-30405
 
-2022-03-02 08:57 +0000 [b6e482becd]  George Joseph <gjoseph@digium.com>
+	  Change-Id: Ib9d9db1ba5b7524594c640461b4aa8f752db8299
 
-	* xml.c, config,c:  Add stylesheets and variable list string parsing
+2023-02-06 09:54 +0000 [98742388b6]  Mike Bradeen <mbradeen@sangoma.com>
 
-	  Added functions to open, close, and apply XML Stylesheets
-	  to XML documents.  Although the presence of libxslt was already
-	  being checked by configure, it was only happening if xmldoc was
-	  enabled.  Now it's checked regardless.
+	* app_senddtmf: Add option to answer target channel.
 
-	  Added ability to parse a string consisting of comma separated
-	  name/value pairs into an ast_variable list.  The reverse of
-	  ast_variable_list_join().
+	  Adds a new option to SendDTMF() which will answer the specified
+	  channel if it is not already up. If no channel is specified, the
+	  current channel will be answered instead.
 
-	  Change-Id: I1e1d149be22165a1fb8e88e2903a36bba1a6cf2e
+	  ASTERISK-30422
 
-2022-03-01 10:58 +0000 [468441121d]  George Joseph <gjoseph@digium.com>
+	  Change-Id: Iddcbd501fcdf9fef0f453b7a8115a90b11f1d085
 
-	* xmldoc: Fix issue with xmlstarlet validation
+2023-02-21 14:25 +0000 [37e558f6ef]  Mike Bradeen <mbradeen@sangoma.com>
 
-	  Added the missing xml-stylesheet and Xinclude namespace
-	  declarations in pjsip_config.xml and pjsip_manager.xml.
+	* res_pjsip: Prevent SEGV in pjsip_evsub_send_request
 
-	  Updated make_xml_documentation to show detailed errors when
-	  xmlstarlet is the validator.  It's now run once with the '-q'
-	  option to suppress harmless/expected messages and if it actually
-	  fails, it's run again without '-q' but with '-e' to show
-	  the actual errors.
+	  contributed pjproject - patch to check sub->pending_notify
+	  in evsub.c:on_tsx_state before calling
+	  pjsip_evsub_send_request()
 
-	  Change-Id: I4bdc9d2ea6741e8d2e5eb82df60c68ccc59e1f5e
+	  res_pjsip_pubsub - change post pjsip 2.13 behavior to use
+	  pubsub_on_refresh_timeout to avoid the ao2_cleanup call on
+	  the sub_tree. This is is because the final NOTIFY send is no
+	  longer the last place the sub_tree is referenced.
 
-2022-02-20 14:16 +0000 [777326fa9e]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-30419
 
-	* core: Config and XML tweaks needed for geolocation
+	  Change-Id: Ib5cc662ce578e9adcda312e16c58a10b6453e438
 
-	  Added:
+2023-02-02 08:19 +0000 [aeb16aa7d8]  Sean Bright <sean@seanbright.com>
 
-	  Replace a variable in a list:
-	  int ast_variable_list_replace_variable(struct ast_variable **head,
-	      struct ast_variable *old, struct ast_variable *new);
-	  Added test as well.
+	* app_queue: Minor docs and logging fixes for UnpauseQueueMember.
 
-	  Create a "name=value" string from a variable list:
-	  'name1="val1",name2="val2"', etc.
-	  struct ast_str *ast_variable_list_join(
-	      const struct ast_variable *head, const char *item_separator,
-	      const char *name_value_separator, const char *quote_char,
-	      struct ast_str **str);
-	  Added test as well.
+	  ASTERISK-30417 #close
 
-	  Allow the name of an XML element to be changed.
-	  void ast_xml_set_name(struct ast_xml_node *node, const char *name);
+	  Change-Id: I7534e7a925bf92a7b5a5347f5f54225768c162fe
 
-	  Change-Id: I330a5f63dc0c218e0d8dfc0745948d2812141ccb
+2023-01-31 08:40 +0000 [aef0c0ce0e]  Sean Bright <sean@seanbright.com>
 
-2022-02-14 07:31 +0000 [a81e14d2da]  George Joseph <gjoseph@digium.com>
+	* app_queue: Reset all queue defaults before reload.
 
-	* Makefile: Allow XML documentation to exist outside source files
+	  Several queue fields were not being set to their default value during
+	  a reload.
 
-	  Moved the xmldoc build logic from the top-level Makefile into
-	  its own script "make_xml_documentation" in the build_tools
-	  directory.
+	  Additionally added some sample configuration options that were missing
+	  from queues.conf.sample.
 
-	  Created a new utility script "get_sourceable_makeopts", also in
-	  the build_tools directory, that dumps the top-level "makeopts"
-	  file in a format that can be "sourced" from shell sscripts.
-	  This allows scripts to easily get the values of common make
-	  build variables such as the location of the GREP, SED, AWK, etc.
-	  utilities as well as the AST* and library *_LIB and *_INCLUDE
-	  variables.
+	  Change-Id: I3a88c7877af91752b1b46a0c087384f7eb9c47e4
 
-	  Besides moving logic out of the Makefile, some optimizations
-	  were done like removing "third-party" from the list of
-	  subdirectories to be searched for documentation and changing some
-	  assignments from "=" to ":=" so they're only evaluated once.
-	  The speed increase is noticeable.
+2023-01-20 16:50 +0000 [58636a6ea6]  Mike Bradeen <mbradeen@sangoma.com>
 
-	  The makeopts.in file was updated to include the paths to
-	  REALPATH and DIRNAME.  The ./conifgure script was setting them
-	  but makeopts.in wasn't including them.
+	* res_pjsip: Upgraded bundled pjsip to 2.13
 
-	  So...
+	  Removed multiple patches.
 
-	  With this change, you can now place documentation in any"c"
-	  source file AND you can now place it in a separate XML file
-	  altogether.  The following are examples of valid locations:
+	  Code chages in res_pjsip_pubsub due to changes in evsub.
 
-	  res/res_pjsip.c
-	      Using the existing /*** DOCUMENTATION ***/ fragment.
+	  Pjsip now calls on_evsub_state() before on_rx_refresh(),
+	  so the sub tree deletion that used to take place in
+	  on_evsub_state() now must take place in on_rx_refresh().
 
-	  res/res_pjsip/pjsip_configuration.c
-	      Using the existing /*** DOCUMENTATION ***/ fragment.
+	  Additionally, pjsip now requires that you send the NOTIFY
+	  from within on_rx_refresh(), otherwise it will assert
+	  when going to send the 200 OK. The idea is that it will
+	  look for this NOTIFY and cache it until after sending the
+	  response in order to deal with the self-imposed message
+	  mis-order. Asterisk previously dealt with this by pushing
+	  the NOTIFY in on_rx_refresh(), but pjsip now forces us
+	  to use it's method.
 
-	  res/res_pjsip/pjsip_doc.xml
-	      A fully-formed XML file.  The "configInfo", "manager",
-	      "managerEvent", etc. elements that would be in the "c"
-	      file DOCUMENTATION fragment should be wrapped in proper
-	      XML.  Example for "somemodule.xml":
+	  Changes were required to configure in order to detect
+	  which way pjsip handles this as the two are not
+	  compatible for the reasons mentioned above.
 
-	      <?xml version="1.0" encoding="UTF-8"?>
-	      <!DOCTYPE docs SYSTEM "appdocsxml.dtd">
-	      <docs>
-	          <configInfo>
-	          ...
-	          </configInfo>
-	      </docs>
+	  A corresponding change in testsuite is required in order
+	  to deal with the small interal timing changes caused by
+	  moving the NOTIFY send.
 
-	  It's the "appdocsxml.dtd" that tells make_xml_documentation
-	  that this is a documentation XML file and not some other XML file.
-	  It also allows many XML-capable editors to do formatting and
-	  validation.
+	  ASTERISK-30325
 
-	  Other than the ".xml" suffix, the name of the file is not
-	  significant.
+	  Change-Id: I50b00cac89d950d3511d7b250a1c641965d9fe7f
 
-	  As a start... This change also moves the documentation that was
-	  in res_pjsip.c to 2 new XML files in res/res_pjsip:
-	  pjsip_config.xml and pjsip_manager.xml.  This cut the number of
-	  lines in res_pjsip.c in half. :)
+2023-01-30 15:17 +0000 [96d9ad51ac]  Sean Bright <sean@seanbright.com>
 
-	  Change-Id: I486c16c0b5a44d7a8870008e10c941fb19b71ade
+	* doxygen: Fix doxygen errors.
 
-2022-02-17 10:26 +0000 [47106a09b0]  George Joseph <gjoseph@digium.com>
+	  Change-Id: Ic50e95b4fc10f74ab15416d908e8a87ee8ec2f85
 
-	* build: Refactor the earlier "basebranch" commit
+2022-01-06 16:11 +0000 [88b2c741ca]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Recap from earlier commit:  If you have a development branch for a
-	  major project that will receive gerrit reviews it'll probably be
-	  named something like "development/16/newproject" or a work branch
-	  based on that "development" branch.  That will necessitate
-	  setting "defaultbranch=development/16/newproject" in .gitreview.
-	  The make_version script uses that variable to construct the
-	  asterisk version however, which results in versions
-	  like "GIT-development/16/newproject-ee582a8c7b" which is probably
-	  not what you want.  It also constructs the URLs for downloading
-	  external modules with that version, which will fail.
+	* app_signal: Add signaling applications
 
-	  Fast-forward:
+	  Adds the Signal and WaitForSignal
+	  applications, which can be used for inter-channel
+	  signaling in the dialplan.
 
-	  The earlier attempt at adding a "basebranch" variable to
-	  .gitreview didn't work out too well in practice because changes
-	  were made to .gitreview, which is a checked-in file.  So, if
-	  you wanted to rebase your work branch on the base branch, rebase
-	  would attempt to overwrite your .gitreview with the one from
-	  the base branch and complain about a conflict.
+	  Signal supports sending a signal to other channels
+	  listening for a signal of the same name, with an
+	  optional data payload. The signal is received by
+	  all channels waiting for that named signal.
 
-	  This is a slighltly different approach that adds three methods to
-	  determine the mainline branch:
+	  ASTERISK-29810 #close
 
-	  1.  --- MAINLINE_BRANCH from the environment
+	  Change-Id: Ic34439de3d60f8609357666a465c354d81f5fef3
 
-	  If MAINLINE_BRANCH is already set in the environment, that will
-	  be used.  This is primarily for the Jenkins jobs.
+2023-01-25 16:27 +0000 [70856e865f]  Mike Bradeen <mbradeen@sangoma.com>
 
-	  2.  --- .develvars
+	* app_directory: add ability to specify configuration file
 
-	  Instead of storing the basebranch in .gitreview, it can now be
-	  stored in a non-checked-in ".develvars" file and keyed by the
-	  current branch.  So, if you were working on a branch named
-	  "new-feature-work" based on "development/16/new-feature" and wanted
-	   to push to that branch in Gerrit but wanted to pull the external
-	   modules for 16, you'd create the following .develvars file:
+	  Adds option to app_directory to specify a filename from which to
+	  read configuration instead of voicemail.conf ie;
 
-	  [branch "new-feature-work"]
-	      mainline-branch = 16
+	  same => n,Directory(,,c(directory.conf))
 
-	  The .gitreview file would still look like:
+	  This configuration should contain a list of extensions using the
+	  voicemail.conf format, ie;
 
-	  [gerrit]
-	  defaultbranch=development/16/new-feature
+	  2020=2020,Dog Dog,,,,attach=no|saycid=no|envelope=no|delete=no
 
-	  ...which would cause any reviews pushed from "new-feature-work" to
-	  go to the "development/16/new-feature" branch in Gerrit.
+	  ASTERISK-30404
 
-	  The key is that the .develvars file is NEVER checked in (it's been
-	  added to .gitignore).
+	  Change-Id: Id58ccb1344ad1e563fa10db12f172fbd104a9d13
 
-	  3.  --- Well Known Development Branch
+2022-02-12 15:59 +0000 [8a45cd7af4]  Naveen Albert <asterisk@phreaknet.org>
 
-	  If you're actually working in a branch named like
-	  "development/<mainline_branch>/some-feature", the mainline branch
-	  will be parsed from it.
+	* func_json: Enhance parsing capabilities of JSON_DECODE
 
-	  4.  --- .gitreview
+	  Adds support for arrays to JSON_DECODE by allowing the
+	  user to print out entire arrays or index a particular
+	  key or print the number of keys in a JSON array.
 
-	  If none of the earlier conditions exist, the .gitreview
-	  "defaultbranch" variable will be used just as before.
+	  Additionally, adds support for recursively iterating a
+	  JSON tree in a single function call, making it easier
+	  to parse JSON results with multiple levels. A maximum
+	  depth is imposed to prevent potentially blowing
+	  the stack.
 
-	  Change-Id: I1cdeeaa0944bba3f2e01d7a2039559d0c266f8c9
+	  Also fixes a bug with the unit tests causing an empty
+	  string to be printed instead of the actual test result.
 
-2022-02-23 07:58 +0000 [3771074a45]  Joshua C. Colp <jcolp@sangoma.com>
+	  ASTERISK-29913 #close
 
-	* jansson: Update bundled to 2.14 version.
+	  Change-Id: I603940b216a3911b498fc6583b18934011ef5d5b
 
-	  ASTERISK-29353
+2023-01-04 06:35 +0000 [f99849f8d5]  sungtae kim <sungtae.kim@avoxi.com>
 
-	  Change-Id: I4ea43eda1691565563a4c03ef37166952d211b2b
+	* res_stasis_snoop: Fix snoop crash
 
-2022-01-09 07:32 +0000 [59b25e9d59]  Naveen Albert <asterisk@phreaknet.org>
+	  Added NULL pointer check and channel lock to prevent resource release
+	  while the chanspy is processing.
 
-	* ami: Allow events to be globally disabled.
+	  ASTERISK-29604
 
-	  The disabledevents setting has been added to the general section
-	  in manager.conf, which allows users to specify events that
-	  should be globally disabled and not sent to any AMI listeners.
+	  Change-Id: Ibdc675f98052da32333b19685b1708a3751b6d24
 
-	  This allows for processing of these AMI events to end sooner and,
-	  for frequent AMI events such as Newexten which users may not have
-	  any need for, allows them to not be processed. Additionally, it also
-	  cleans up core debug as previously when debug was 3 or higher,
-	  the debug was constantly spammed by "Analyzing AMI event" messages
-	  along with a complete dump of the event contents (often for Newexten).
+2023-01-26 14:18 +0000 [56051d1ac5]  Sean Bright <sean@seanbright.com>
 
-	  ASTERISK-29853 #close
+	* pbx_ael: Global variables are not expanded.
 
-	  Change-Id: Id42b9a3722a1f460d745cad1ebc47c537fd4f205
+	  Variable references within global variable assignments are now
+	  expanded rather than being included literally.
 
-2022-01-06 07:57 +0000 [42525b0fe2]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-30406 #close
 
-	* func_channel: Add lastcontext and lastexten.
+	  Change-Id: I136e8d6395e90a4c92d9777a46a7bc3edb08d05d
 
-	  Adds the lastcontext and lastexten channel fields to allow users
-	  to access previous dialplan execution locations.
+2022-10-13 08:45 +0000 [a1da8042d1]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29840 #close
+	* res_pjsip_session: Add overlap_context option.
 
-	  Change-Id: Ib455fe300cc8e9a127686896ee2d0bd11e900307
+	  Adds the overlap_context option, which can be used
+	  to explicitly specify a context to use for overlap
+	  dialing extension matches, rather than forcibly
+	  using the context configured for the endpoint.
 
-2022-02-04 19:27 +0000 [7f123b3143]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-30262 #close
 
-	* channel.c: Clean up debug level 1.
+	  Change-Id: Ibbcd4a8b11402428a187fb56b8d4e7408774a0db
 
-	  Although there are 10 debugs levels, over time,
-	  many current debug calls have come to use
-	  inappropriately low debug levels. In particular,
-	  a select few debug calls (currently all debug 1)
-	  can result in thousands of debug messages per minute
-	  for a single call.
+2023-01-05 10:41 +0000 [ef16eaee36]  Sean Bright <sean@seanbright.com>
 
-	  This can adds a lot of noise to core debug
-	  which dilutes the value in having different
-	  debug levels in the first place, as these
-	  log messages are from the core internals are
-	  are better suited for higher debug levels.
+	* app_playback.c: Fix PLAYBACKSTATUS regression.
 
-	  Some debugs levels are thus adjusted so that
-	  debug level 1 is not inappropriately overloaded
-	  with these extremely high-volume and general
-	  debug messages.
+	  In Asterisk 11, if a channel was redirected away during Playback(),
+	  the PLAYBACKSTATUS variable would be set to SUCCESS. In Asterisk 12
+	  (specifically commit 7d9871b3940fa50e85039aef6a8fb9870a7615b9) that
+	  behavior was inadvertently changed and the same operation would result
+	  in the PLAYBACKSTATUS variable being set to FAILED. The Asterisk 11
+	  behavior has been restored.
 
-	  ASTERISK-29897 #close
+	  Partial fix for ASTERISK~25661.
 
-	  Change-Id: I55a71598993552d3d64a401a35ee99474770d4b4
+	  Change-Id: I53f54e56b59b61c99403a481b6cb8d88b5a559ff
 
-2022-02-17 13:47 +0000 [8631e00a41]  Naveen Albert <asterisk@phreaknet.org>
+2023-01-11 11:17 +0000 [2f5aece0c9]  George Joseph <gjoseph@digium.com>
 
-	* configs, LICENSE: remove pbx.digium.com.
+	* res_rtp_asterisk: Don't use double math to generate timestamps
 
-	  pbx.digium.com no longer accepts IAX2 calls and
-	  there are no plans for it to come back.
+	  Rounding issues with double math were causing rtp timestamp
+	  slips in outgoing packets.  We're now back to integer math
+	  and are getting no more slips.
 
-	  Accordingly, nonworking IAX2 URIs are removed from
-	  both the LICENSE file and the sample config.
+	  ASTERISK-30391
 
-	  ASTERISK-29923 #close
+	  Change-Id: I6ba992b49ffdf9ebea074581dfa784a188c661a4
 
-	  Change-Id: I257c54d4d812ed6b4bd4cbec2cd7ebe2b87b5bad
+2023-01-06 10:06 +0000 [e86d5d7fda]  Alexei Gradinari <alex2grad@gmail.com>
 
-2022-02-04 19:11 +0000 [3499aea882]  Naveen Albert <asterisk@phreaknet.org>
+	* format_wav: replace ast_log(LOG_DEBUG, ...) by ast_debug(1, ...)
 
-	* documentation: Add since tag to xmldocs DTD
+	  Each playback of WAV files results in logging
+	  "Skipping unknown block 'LIST'".
 
-	  Adds the since tag to the documentation DTD so
-	  that individual applications, functions, etc.
-	  can now specify when they were added to Asterisk.
+	  To prevent unnecessary flooding of this DEBUG log this patch replaces
+	  ast_log(LOG_DEBUG, ...) by ast_debug(1, ...).
 
-	  This tag is added at the individual application,
-	  function, etc. level as opposed to at the module
-	  level because modules can expand over time as new
-	  functionality is added, and granularity only
-	  to the module level would generally not be useful.
+	  Change-Id: Iaa09cf19c5348a05385518fdb8cb181b45fe05f0
 
-	  This enables the ability to more easily determine
-	  when new functionality was added to Asterisk, down
-	  to minor version as opposed to just by major version.
-	  This makes it easier for users to write more portable
-	  dialplan if desired to not use functionality that may
-	  not be widely available yet.
+2022-11-17 20:16 +0000 [3526441e41]  Igor Goncharovsky <igorg@iqtek.ru>
 
-	  ASTERISK-29896 #close
+	* res_pjsip_rfc3326: Add SIP causes support for RFC3326
 
-	  Change-Id: Ibbb35c702d8038bdc3fd0a944fbfa69384cc15d5
+	  Add ability to set HANGUPCAUSE when SIP causecode received in BYE (in addition to currently supported Q.850).
 
-2022-01-13 08:37 +0000 [63db7505f2]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-30319 #close
 
-	* asterisk: Add macro for curl user agent.
+	  Change-Id: I3f55622dc680ce713a2ffb5a458ef5dd39fcf645
 
-	  Currently, each module that uses libcurl duplicates the standard
-	  Asterisk curl user agent.
+2022-10-28 05:57 +0000 [4710f37ef6]  George Joseph <gjoseph@digium.com>
 
-	  This adds a global macro for the Asterisk user agent used for
-	  curl requests to eliminate this duplication.
+	* res_rtp_asterisk: Asterisk Media Experience Score (MES)
 
-	  ASTERISK-29861 #close
+	  -----------------
 
-	  Change-Id: I9fc37935980384b4daf96ae54fa3c9adb962ed2d
+	  This commit reinstates MES with some casting fixes to the
+	  functions in time.h that convert between doubles and timeval
+	  structures.  The casting issues were causing incorrect
+	  timestamps to be calculated which caused transcoding from/to
+	  G722 to produce bad or no audio.
 
-2021-12-16 13:41 +0000 [74742cdb5e]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-30391
 
-	* res_stir_shaken: refactor utility function
+	  -----------------
 
-	  Refactors temp file utility function into file.c.
+	  This module has been updated to provide additional
+	  quality statistics in the form of an Asterisk
+	  Media Experience Score.  The score is avilable using
+	  the same mechanisms you'd use to retrieve jitter, loss,
+	  and rtt statistics.  For more information about the
+	  score and how to retrieve it, see
+	  https://wiki.asterisk.org/wiki/display/AST/Media+Experience+Score
 
-	  ASTERISK-29809 #close
+	  * Updated chan_pjsip to set quality channel variables when a
+	    call ends.
+	  * Updated channels/pjsip/dialplan_functions.c to add the ability
+	    to retrieve the MES along with the existing rtcp stats when
+	    using the CHANNEL dialplan function.
+	  * Added the ast_debug_rtp_is_allowed and ast_debug_rtcp_is_allowed
+	    checks for debugging purposes.
+	  * Added several function to time.h for manipulating time-in-samples
+	    and times represented as double seconds.
+	  * Updated rtp_engine.c to pass through the MES when stats are
+	    requested.  Also debug output that dumps the stats when an
+	    rtp instance is destroyed.
+	  * Updated res_rtp_asterisk.c to implement the calculation of the
+	    MES.  In the process, also had to update the calculation of
+	    jitter.  Many debugging statements were also changed to be
+	    more informative.
+	  * Added a unit test for internal testing.  The test should not be
+	    run during normal operation and is disabled by default.
 
-	  Change-Id: Ife478708c8f2b127239cb73c1755ef18c0bf431b
+	  Change-Id: I4fce265965e68c3fdfeca55e614371ee69c65038
 
-2022-02-16 05:34 +0000 [2016b33139]  Naveen Albert <asterisk@phreaknet.org>
+2023-01-09 07:21 +0000 [62ca063fca]  George Joseph <gjoseph@digium.com>
 
-	* app_voicemail: Emit warning if asking for nonexistent mailbox.
+	* Revert "res_rtp_asterisk: Asterisk Media Experience Score (MES)"
 
-	  Currently, if VoiceMailMain is called with a mailbox, if that
-	  mailbox doesn't exist, then the application silently falls back
-	  to prompting the user for the mailbox, as if no arguments were
-	  provided.
+	  This reverts commit d454801c2ddba89f7925c847012db2866e271f68.
 
-	  However, if a specific mailbox is requested and it doesn't exist,
-	  then no warning at all is emitted.
+	  Reason for revert: Issue when transcoding to/from g722
 
-	  This fixes this behavior to now warn if a specifically
-	  requested mailbox could not be accessed, before falling back to
-	  prompting the user for the correct mailbox.
+	  Change-Id: I09f49e171b1661548657a9ba7a978c29d0b5be86
 
-	  ASTERISK-29920 #close
+2022-12-08 15:44 +0000 [d33bd6d67a]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Ib4093b88cd661a2cabc5d685777d4e2f0ebd20a4
+	* loader: Allow declined modules to be unloaded.
 
-2022-02-07 16:31 +0000 [1cc1fb54e7]  Alexei Gradinari <alex2grad@gmail.com>
+	  Currently, if a module declines to load, dlopen is called
+	  to register the module but dlclose never gets called.
+	  Furthermore, loader.c currently doesn't allow dlclose
+	  to ever get called on the module, since it declined to
+	  load and the unload function bails early in this case.
 
-	* res_pjsip_pubsub: fix Batched Notifications stop working
+	  This can be problematic if a module is updated, since the
+	  new module cannot be loaded into memory since we haven't
+	  closed all references to it. To fix this, we now allow
+	  modules to be unloaded, even if they never "loaded" in
+	  Asterisk itself, so that dlclose is called and the module
+	  can be properly cleaned up, allowing the updated module
+	  to be loaded from scratch next time.
 
-	  If Subscription refresh occurred between when the batched notification
-	  was scheduled and the serialized notification was to be sent,
-	  then new schedule notification task would never be added.
+	  ASTERISK-30345 #close
 
-	  There are 2 threads:
+	  Change-Id: Ifc743aadfa85ebe3284e02a63e124dafa64988d5
 
-	  thread #1. ast_sip_subscription_notify is called,
-	  if notification_batch_interval then call schedule_notification.
-	  1.1. The schedule_notification checks notify_sched_id > -1
-	  not true, then
-	  send_scheduled_notify = 1
-	  notify_sched_id =
-	    ast_sched_add(sched, sub_tree->notification_batch_interval, sched_cb....
-	  1.2. The sched_cb pushes task serialized_send_notify to serializer
-	  and returns 0 which means no reschedule.
-	  1.3. The serialized_send_notify checks send_scheduled_notify if it's false
-	  the just returns. BUT notify_sched_id is still set, so no more ast_sched_add.
+2022-08-15 15:04 +0000 [e06fe8e344]  Naveen Albert <asterisk@phreaknet.org>
 
-	  thread #2. pubsub_on_rx_refresh is called
-	  2.1 it pushes serialized_pubsub_on_refresh_timeout to serializer
-	  2.2. The serialized_pubsub_on_refresh_timeout calls pubsub_on_refresh_timeout
-	  which calls send_notify
-	  2.3. The send_notify set send_scheduled_notify = 0;
+	* app_broadcast: Add Broadcast application
 
-	  The serialized_send_notify should always unset notify_sched_id.
+	  Adds a new application, Broadcast, which can be used for
+	  one-to-many transmission and many-to-one reception of
+	  channel audio in Asterisk. This is similar to ChanSpy,
+	  except it is designed for multiple channel targets instead
+	  of a single one. This can make certain kinds of audio
+	  manipulation more efficient and streamlined. New kinds
+	  of audio injection impossible with ChanSpy are also made
+	  possible.
 
-	  ASTERISK-29904 #close
+	  ASTERISK-30180 #close
 
-	  Change-Id: Ifc50c00b213c396509e10326a1ed89d8cf8c7875
+	  Change-Id: I7ba72f765dbab9b58deeae028baca3f4f8377726
 
-2022-02-18 06:09 +0000 [26141981c5]  Naveen Albert <asterisk@phreaknet.org>
+2022-12-13 14:35 +0000 [68e345286b]  Naveen Albert <asterisk@phreaknet.org>
 
-	* func_db: Add validity check for key names when writing.
+	* func_frame_trace: Print text for text frames.
 
-	  Adds a simple sanity check for key names when users are
-	  writing data to AstDB. This captures four cases indicating
-	  malformed keynames that generally result in bad data going
-	  into the DB that the user didn't intend: an empty key name,
-	  a key name beginning or ending with a slash, and a key name
-	  containing two slashes in a row. Generally, this is the
-	  result of a variable being used in the key name being empty.
+	  Since text frames contain a text body, make FRAME_TRACE
+	  more useful for text frames by actually printing the text.
 
-	  If a malformed key name is detected, a warning is emitted
-	  to indicate the bug in the dialplan.
+	  ASTERISK-30353 #close
 
-	  ASTERISK-29925 #close
+	  Change-Id: Ia6ce3d15cecd7a673a528d34faac86854a2bab50
 
-	  Change-Id: Ifc08a9fe532a519b1b80caca1aafed7611d573bf
+2022-12-16 12:25 +0000 [3b3fef2347]  Naveen Albert <asterisk@phreaknet.org>
 
-2022-02-01 09:59 +0000 [e2423c6f49]  Alexei Gradinari <alex2grad@gmail.com>
+	* json.h: Add ast_json_object_real_get.
 
-	* res_pjsip_pubsub: provide a display name for RLS subscriptions
+	  json.h contains macros to get a string and an integer
+	  from a JSON object. However, the macro to do this for
+	  JSON reals is missing. This adds that.
 
-	  Whereas BLFs allow to show a display name for each RLS entry,
-	  the asterisk provides only the extension now.
-	  This is not end user friendly.
+	  ASTERISK-30361 #close
 
-	  This commit adds a new resource_list option, resource_display_name,
-	  to indicate whether display name of resource or the resource name being
-	  provided for RLS entries.
-	  If this option is enabled, the Display Name will be provided.
-	  This option is disabled by default to remain the previous behavior.
-	  If the 'event' set to 'presence' or 'dialog' the non-empty HINT name
-	  will be set as the Display Name.
-	  The 'message-summary' is not supported yet.
+	  Change-Id: I8d0e28d763febf27b05801cdc83b73282aa6ee7a
 
-	  ASTERISK-29891 #close
+2022-12-21 19:01 +0000 [7b8f7428da]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Ic5306bd5a7c73d03f5477fe235e9b0f41c69c681
+	* manager: Fix appending variables.
 
-2022-01-13 19:37 +0000 [4358c776b8]  Naveen Albert <asterisk@phreaknet.org>
+	  The if statement here is always false after the for
+	  loop finishes, so variables are never appended.
+	  This removes that to properly append to the end
+	  of the variable list.
 
-	* cli: Add core dump info to core show settings.
+	  ASTERISK-30351 #close
+	  Reported by: Sebastian Gutierrez
 
-	  Adds two pieces of information to the core show settings command
-	  which are useful in the context of getting backtraces.
+	  Change-Id: I1b7f8b85a8918f6a814cb933a479d4278cf16199
 
-	  The first is to display whether or not Asterisk would generate
-	  a core dump if it were to crash.
+2022-12-23 06:02 +0000 [24102ba236]  George Joseph <gjoseph@digium.com>
 
-	  The second is to show the current running directory of Asterisk.
+	* res_pjsip_transport_websocket: Add remote port to transport
 
-	  ASTERISK-29866 #close
+	  When Asterisk receives a new websocket conenction, it creates a new
+	  pjsip transport for it and copies connection data into it.  The
+	  transport manager then uses the remote IP address and port on the
+	  transport to create a monitor for each connection.  However, the
+	  remote port wasn't being copied, only the IP address which meant
+	  that the transport manager was creating only 1 monitoring entry for
+	  all websocket connections from the same IP address. Therefore, if
+	  one of those connections failed, it deleted the transport taking
+	  all the the connections from that same IP address with it.
 
-	  Change-Id: Ic42c0a9ecc233381aad274d86c62808d1ebb4d83
+	  * We now copy the remote port into the created transport and the
+	    transport manager behaves correctly.
 
-2022-02-04 19:46 +0000 [74e9b60bd0]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-30369
 
-	* documentation: Adds missing default attributes.
+	  Change-Id: Ib506d40897ea6286455ac0be4dfbb0ed43b727e1
 
-	  The configObject tag contains a default attribute which
-	  allows the default value to be specified, if applicable.
-	  This allows for the default value to show up specially on
-	  the wiki in a way that is clear to users.
+2022-12-28 13:33 +0000 [edc90c96ac]  Boris P. Korzun <drtr0jan@yandex.ru>
 
-	  There are a couple places in the tree where default values
-	  are included in the description as opposed to as attributes,
-	  which means these can't be parsed specially for the wiki.
-	  These are changed to use the attribute instead of being
-	  included in the text description.
+	* http.c: Fix NULL pointer dereference bug
 
-	  ASTERISK-29898 #close
+	  If native HTTP is disabled but HTTPS is enabled and status page enabled
+	  too, Core/HTTP crashes while loading. 'global_http_server' references
+	  to NULL, but the status page tries to dereference it.
 
-	  Change-Id: I9d7ea08f50075f41459ea7b76654906b674ec755
+	  The patch adds a check for HTTP is enabled.
 
-2022-02-05 06:39 +0000 [da801e2438]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-30379 #close
 
-	* app_mp3: Document and warn about HTTPS incompatibility.
+	  Change-Id: I11b02fc920b72aaed9c809fc43210523ccfdc249
 
-	  mpg123 doesn't support HTTPS, but the MP3Player application
-	  doesn't document this or warn the user about this. HTTPS
-	  streams have become more common nowadays and users could
-	  reasonably try to play them without being aware they should
-	  use the HTTP stream instead.
+2022-12-16 01:00 +0000 [3d9b9a2b16]  Holger Hans Peter Freyther <holger@moiji-mobile.com>
 
-	  This adds documentation to note this limitation. It also
-	  throws a warning if users try to use the HTTPS stream to
-	  tell them to use the HTTP stream instead.
+	* res_http_media_cache: Do not crash when there is no extension
 
-	  ASTERISK-29900 #close
+	  Do not crash when a URL has no path component as in this case the
+	  ast_uri_path function will return NULL. Make the code cope with not
+	  having a path.
 
-	  Change-Id: Ie3b029be5258c5a701f71ed3b1a7a80d1e03b827
+	  The below would crash
+	  > media cache create http://google.com /tmp/foo.wav
 
-2022-01-22 16:52 +0000 [332eed3aa7]  Naveen Albert <asterisk@phreaknet.org>
+	  Thread 1 "asterisk" received signal SIGSEGV, Segmentation fault.
+	  0x0000ffff836616cc in strrchr () from /lib/aarch64-linux-gnu/libc.so.6
+	  (gdb) bt
+	   #0  0x0000ffff836616cc in strrchr () from /lib/aarch64-linux-gnu/libc.so.6
+	   #1  0x0000ffff43d43a78 in file_extension_from_string (str=<optimized out>, buffer=buffer@entry=0xffffca9973c0 "",
+	      capacity=capacity@entry=64) at res_http_media_cache.c:288
+	   #2  0x0000ffff43d43bac in file_extension_from_url_path (bucket_file=bucket_file@entry=0x3bf96568,
+	      buffer=buffer@entry=0xffffca9973c0 "", capacity=capacity@entry=64) at res_http_media_cache.c:378
+	   #3  0x0000ffff43d43c74 in bucket_file_set_extension (bucket_file=bucket_file@entry=0x3bf96568) at res_http_media_cache.c:392
+	   #4  0x0000ffff43d43d10 in bucket_file_run_curl (bucket_file=0x3bf96568) at res_http_media_cache.c:555
+	   #5  0x0000ffff43d43f74 in bucket_http_wizard_create (sorcery=<optimized out>, data=<optimized out>, object=<optimized out>)
+	      at res_http_media_cache.c:613
+	   #6  0x0000000000487638 in bucket_file_wizard_create (sorcery=<optimized out>, data=<optimized out>, object=<optimized out>)
+	      at bucket.c:191
+	   #7  0x0000000000554408 in sorcery_wizard_create (object_wizard=object_wizard@entry=0x3b9f0718,
+	      details=details@entry=0xffffca9974a8) at sorcery.c:2027
+	   #8  0x0000000000559698 in ast_sorcery_create (sorcery=<optimized out>, object=object@entry=0x3bf96568) at sorcery.c:2077
+	   #9  0x00000000004893a4 in ast_bucket_file_create (file=file@entry=0x3bf96568) at bucket.c:727
+	   #10 0x00000000004f877c in ast_media_cache_create_or_update (uri=0x3bfa1103 "https://google.com",
+	      file_path=0x3bfa1116 "/tmp/foo.wav", metadata=metadata@entry=0x0) at media_cache.c:335
+	   #11 0x00000000004f88ec in media_cache_handle_create_item (e=<optimized out>, cmd=<optimized out>, a=0xffffca9976b8)
+	      at media_cache.c:640
 
-	* app_mf: Add max digits option to ReceiveMF.
+	  ASTERISK-30375 #close
 
-	  Adds an option to the ReceiveMF application to allow specifying a
-	  maximum number of digits.
+	  Change-Id: I6a9433688cb5d3d4be8758b7642d923bdde6c273
 
-	  Originally, this capability was not added to ReceiveMF as it was
-	  with ReceiveSF because typically a ST digit is used to denote that
-	  sending of digits is complete. However, there are certain signaling
-	  protocols which simply transmit a digit (such as Expanded In-Band
-	  Signaling) and for these, it's necessary to be able to read a
-	  certain number of digits, as opposed to until receiving a ST digit.
+2022-10-28 05:57 +0000 [d454801c2d]  George Joseph <gjoseph@digium.com>
 
-	  This capability is added as an option, as opposed to as a parameter,
-	  to remain compatible with existing usage (and not shift the
-	  parameters).
+	* res_rtp_asterisk: Asterisk Media Experience Score (MES)
 
-	  ASTERISK-29877 #close
+	  This module has been updated to provide additional
+	  quality statistics in the form of an Asterisk
+	  Media Experience Score.  The score is avilable using
+	  the same mechanisms you'd use to retrieve jitter, loss,
+	  and rtt statistics.  For more information about the
+	  score and how to retrieve it, see
+	  https://wiki.asterisk.org/wiki/display/AST/Media+Experience+Score
 
-	  Change-Id: I4229167c9aa69b87402c3c2a9065bd8dfa973a0b
+	  * Updated chan_pjsip to set quality channel variables when a
+	    call ends.
+	  * Updated channels/pjsip/dialplan_functions.c to add the ability
+	    to retrieve the MES along with the existing rtcp stats when
+	    using the CHANNEL dialplan function.
+	  * Added the ast_debug_rtp_is_allowed and ast_debug_rtcp_is_allowed
+	    checks for debugging purposes.
+	  * Added several function to time.h for manipulating time-in-samples
+	    and times represented as double seconds.
+	  * Updated rtp_engine.c to pass through the MES when stats are
+	    requested.  Also debug output that dumps the stats when an
+	    rtp instance is destroyed.
+	  * Updated res_rtp_asterisk.c to implement the calculation of the
+	    MES.  In the process, also had to update the calculation of
+	    jitter.  Many debugging statements were also changed to be
+	    more informative.
+	  * Added a unit test for internal testing.  The test should not be
+	    run during normal operation and is disabled by default.
+
+	  ASTERISK-30280
+
+	  Change-Id: I458cb9a311e8e5dc1db769b8babbcf2e093f107a
+
+2022-12-21 09:01 +0000 [cc8d9b947b]  Naveen Albert <asterisk@phreaknet.org>
+
+	* pbx_app: Update outdated pbx_exec channel snapshots.
 
-2022-02-02 19:18 +0000 [9e71f7fe37]  Mike Bradeen <mbradeen@sangoma.com>
+	  pbx_exec makes a channel snapshot before executing applications.
+	  This doesn't cause an issue during normal dialplan execution
+	  where pbx_exec is called over and over again in succession.
+	  However, if pbx_exec is called "one off", e.g. using
+	  ast_pbx_exec_application, then a channel snapshot never ends
+	  up getting made after the executed application returns, and
+	  inaccurate snapshot information will linger for a while, causing
+	  "core show channels", etc. to show erroneous info.
+
+	  This is fixed by manually making a channel snapshot at the end
+	  of ast_pbx_exec_application, since we anticipate that pbx_exec
+	  might not get called again immediately.
+
+	  ASTERISK-30367 #close
+
+	  Change-Id: I2a5131053aa9d11badbc0ef2ef40b1f83d0af086
+
+2022-11-26 06:54 +0000 [c7598ee947]  Naveen Albert <asterisk@phreaknet.org>
+
+	* res_pjsip_session: Use Caller ID for extension matching.
+
+	  Currently, there is no Caller ID available to us when
+	  checking for an extension match when handling INVITEs.
+	  As a result, extension patterns that depend on the Caller ID
+	  are not matched and calls may be incorrectly rejected.
+
+	  The Caller ID is not available because the supplement that
+	  adds Caller ID to the session does not execute until after
+	  this check. Supplement callbacks cannot yet be executed
+	  at this point since the session is not yet in the appropriate
+	  state.
 
-	* taskprocessor.c: Prevent crash on graceful shutdown
+	  To fix this without impacting existing behavior, the Caller ID
+	  number is now retrieved before attempting to pattern match.
+	  This ensures pattern matching works correctly and there is
+	  no behavior change to the way supplements are called.
 
-	  When tps_shutdown is called as part of the cleanup process there is a
-	  chance that one of the taskprocessors that references the
-	  tps_singletons object is still running.  The change is to allow for
-	  tps_shutdown to check tps_singleton's container count and give the
-	  running taskprocessors a chance to finish.  If after
-	  AST_TASKPROCESSOR_SHUTDOWN_MAX_WAIT (10) seconds there are still
-	  container references we shutdown anyway as this is most likely a bug
-	  due to a taskprocessor not being unreferenced.
+	  ASTERISK-28767 #close
 
-	  ASTERISK-29365
+	  Change-Id: Iec7f5a3b90e51b65ccf74342f96bf80314b7cfc7
 
-	  Change-Id: Ia932fc003d316389b9c4fd15ad6594458c9727f1
+2022-12-12 12:42 +0000 [881faf544f]  Ben Ford <bford@digium.com>
 
-2022-01-21 13:00 +0000 [fcdeb3e5b7]  Alexei Gradinari <alex2grad@gmail.com>
+	* res_pjsip_sdp_rtp.c: Use correct timeout when put on hold.
 
-	* app_queue: load queues and members from Realtime when needed
+	  When a call is put on hold and it has moh_passthrough and rtp_timeout
+	  set on the endpoint, the wrong timeout will be used. rtp_timeout_hold is
+	  expected to be used, but rtp_timeout is used instead. This change adds a
+	  couple of checks for locally_held to determine if rtp_timeout_hold needs
+	  to be used instead of rtp_timeout.
 
-	  There are a lot of Queue AMI actions and Queue applications
-	  which do not load queue and queue members from Realtime.
+	  ASTERISK-30350
 
-	  AMI actions
-	  QueuePause - if queue not in memory - response "Interface not found".
-	  QueueStatus/QueueSummary - if queue not in memory - empty response.
+	  Change-Id: I7b106fc244332014216d12bba851cefe884cc25f
 
-	  Applications:
-	  PauseQueueMember - if queue not in memory
-	  	Attempt to pause interface %s, not found
-	  UnpauseQueueMember - if queue not in memory
-	  	Attempt to unpause interface xxxxx, not found
+2022-11-14 07:12 +0000 [20d4775d0a]  Naveen Albert <asterisk@phreaknet.org>
 
-	  This patch adds a new function load_realtime_queues
-	  which loads queue and queue members for desired queue
-	  or all queues and all members if param 'queuename' is NULL or empty.
-	  Calls the function load_realtime_queues when needed.
+	* app_voicemail_odbc: Fix string overflow warning.
 
-	  Also this patch fixes leak of ast_config in function set_member_value.
+	  Fixes a negative offset warning by initializing
+	  the buffer to empty.
 
-	  Also this patch fixes incorrect LOG_WARNING when pausing/unpausing
-	  already paused/unpaused member.
-	  The function ast_update_realtime returns 0 when no record modified.
-	  So 0 is not an error to warn about.
+	  Additionally, although it doesn't currently complain
+	  about it, the size of a buffer is increased to
+	  accomodate the maximum size contents it could have.
 
-	  ASTERISK-29873 #close
-	  ASTERISK-18416 #close
-	  ASTERISK-27597 #close
+	  ASTERISK-30240 #close
 
-	  Change-Id: I554ee0eebde93bd8f49df7f84b74acb21edcb99c
+	  Change-Id: I8eecedf14d3f2a75864797f802277cac89a32877
 
-2022-01-21 07:52 +0000 [6659e502a4]  Mark Petersen <bugs.digium.com@zombie.dk>
+2022-11-25 18:03 +0000 [cbb1fd2cb9]  Naveen Albert <asterisk@phreaknet.org>
 
-	* res_prometheus.c: missing module dependency
+	* func_callerid: Warn about invalid redirecting reason.
 
-	  added res_pjsip_outbound_registration to .requires in AST_MODULE_INFO
-	  which fixes issue with module crashes on load "FRACK!, Failed assertion"
+	  Currently, if a user attempts to set a Caller ID related
+	  function to an invalid value, a warning is emitted,
+	  except for when setting the redirecting reason.
+	  We now emit a warning if we were unable to successfully
+	  parse the user-provided reason.
 
-	  ASTERISK-29871
+	  ASTERISK-30332 #close
 
-	  Change-Id: Ia0f49d048427a40e1b763296b834a52a03610096
+	  Change-Id: Ic341f5d5f7303b6f1115549be64db58a85944f5a
 
-2022-02-07 10:55 +0000 [acd6f30cc5]  Sean Bright <sean.bright@gmail.com>
+2022-11-04 05:11 +0000 [115a1b4f0a]  Igor Goncharovsky <igorg@iqtek.ru>
 
-	* manager.c: Simplify AMI ModuleCheck handling
+	* res_pjsip: Fix path usage in case dialing with '@'
 
-	  This code was needlessly complex and would fail to properly delimit
-	  the response message if LOW_MEMORY was defined.
+	  Fix aor lookup on sip path addition. Issue happens in case of dialing
+	  with @ and overriding user part of RURI.
 
-	  Change-Id: Iae50bf09ef4bc34f9dc4b49435daa76f8b2c5b6e
+	  ASTERISK-30100 #close
+	  Reported-by: Yury Kirsanov
 
-2022-02-03 15:48 +0000 [de0c29de55]  Sean Bright <sean.bright@gmail.com>
+	  Change-Id: I3f2c42a583578c94397b113e32ca3ebf2d600e13
 
-	* res_pjsip.c: Correct minor typos in 'realm' documentation.
+2022-11-21 21:37 +0000 [58404b5c22]  Peter Fern <asterisk@obfusc8.org>
 
-	  Change-Id: I886936b808def5540d40071321e72f6bfa19063a
+	* streams:  Ensure that stream is closed in ast_stream_and_wait on error
 
-2022-01-31 12:52 +0000 [118e034287]  Sean Bright <sean.bright@gmail.com>
+	  When ast_stream_and_wait returns an error (for example, when attempting
+	  to stream to a channel after hangup) the stream is not closed, and
+	  callers typically do not check the return code. This results in leaking
+	  file descriptors, leading to resource exhaustion.
 
-	* manager.c: Generate valid XML if attribute names have leading digits.
+	  This change ensures that the stream is closed in case of error.
 
-	  The XML Manager Event Interface (amxml) now generates attribute names
-	  that are compliant with the XML 1.1 specification. Previously, an
-	  attribute name that started with a digit would be rendered as-is, even
-	  though attribute names must not begin with a digit. We now prefix
-	  attribute names that start with a digit with an underscore ('_') to
-	  prevent XML validation failures.
+	  ASTERISK-30198 #close
+	  Reported-by: Julien Alie
 
-	  This is not backwards compatible but my assumption is that compliant
-	  XML parsers would already have been complaining about this.
+	  Change-Id: Ie46b67314590ad75154595a3d34d461060b2e803
 
-	  ASTERISK-29886 #close
+2022-12-10 16:51 +0000 [36bea9ad33]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Icfaa56a131a082d803e9b7db5093806d455a0523
+	* app_sendtext: Remove references to removed applications.
 
-2022-02-03 12:25 +0000  Asterisk Development Team <asteriskteam@digium.com>
+	  Removes see-also references to applications that don't
+	  exist anymore (removed in Asterisk 19),
+	  so these dead links don't show up on the wiki.
 
-	* asterisk 18.10.0-rc1 Released.
+	  ASTERISK-30347 #close
 
-2022-02-03 06:12 +0000 [aa6a50630f]  Asterisk Development Team <asteriskteam@digium.com>
+	  Change-Id: I9539bc30f57cd65aa4e2d5ce8185eafa09567909
 
-	* Update CHANGES and UPGRADE.txt for 18.10.0
-2022-02-01 10:09 +0000 [c51353e4db]  Sean Bright <sean.bright@gmail.com>
+2022-12-15 12:55 +0000  Asterisk Development Team <asteriskteam@digium.com>
 
-	* build_tools/make_version: Fix bashism in comparison.
+	* asterisk 20.1.0-rc1 Released.
 
-	  In POSIX sh (which we indicate in the shebang), there is no ==
-	  operator.
+2022-12-15 06:40 +0000 [fefc236e7c]  Asterisk Development Team <asteriskteam@digium.com>
 
-	  Change-Id: Ic03d38214d14cdf329b0ba272279a815bb532965
+	* Update CHANGES and UPGRADE.txt for 20.1.0
+2022-12-09 13:37 +0000 [01b3962201]  Alexandre Fournier <afournier@wazo.io>
 
-2022-01-21 14:08 +0000 [0d53ce35f9]  George Joseph <gjoseph@digium.com>
+	* res_geoloc: fix NULL pointer dereference bug
 
-	* bundled_pjproject:  Add additional multipart search utils
+	  The `ast_geoloc_datastore_add_eprofile` function does not return 0 on
+	  success, it returns the size of the underlying datastore. This means
+	  that the datastore will be freed and its pointer set to NULL when no
+	  error occured at all.
 
-	  Added the following APIs:
-	  pjsip_multipart_find_part_by_header()
-	  pjsip_multipart_find_part_by_header_str()
-	  pjsip_multipart_find_part_by_cid_str()
-	  pjsip_multipart_find_part_by_cid_uri()
+	  ASTERISK-30346
 
-	  Change-Id: I6aee3dcf59eb171f93aae0f0564ff907262ef40d
+	  Change-Id: Iea9b209bd1244cc57b903b9496cb680c356e4bb9
 
-2022-01-07 04:01 +0000 [95ee1d06d6]  Mark Petersen <bugs.digium.com@zombie.dk>
+2022-12-13 09:25 +0000 [b6855755ce]  Joshua C. Colp <jcolp@sangoma.com>
 
-	* chan_sip.c Fix pickup on channel that are in AST_STATE_DOWN
+	* res_pjsip_aoc: Don't assume a body exists on responses.
 
-	  resolve issue with pickup on device that uses "183" and not "180"
+	  When adding AOC to an outgoing response the code
+	  assumed that a body would exist for comparing the
+	  Content-Type. This isn't always true.
 
-	  ASTERISK-29832
+	  The code now checks to make sure the response has
+	  a body before checking the Content-Type.
 
-	  Change-Id: I4c7d223870f8ce9a7354e0f73d4e4cb2e8b58841
+	  ASTERISK-21502
 
-2022-01-31 07:09 +0000 [2a34bb1e11]  George Joseph <gjoseph@digium.com>
+	  Change-Id: Iaead371434fc3bc693dad487228106a7d7a5ac76
 
-	* res_pjsip_outbound_authenticator_digest: Prevent ABRT on cleanup
+2022-12-12 09:16 +0000 [2f9cdfbc50]  Naveen Albert <asterisk@phreaknet.org>
 
-	  In dev mode, if you call pjsip_auth_clt_deinit() with an auth_sess
-	  that hasn't been initialized, it'll assert and abort.  If
-	  digest_create_request_with_auth() fails to find the proper
-	  auth object however, it jumps to its cleanup which does exactly
-	  that.  So now we no longer attempt to call pjsip_auth_clt_deinit()
-	  if we never actually initialized it.
+	* app_if: Fix format truncation errors.
 
-	  ASTERISK-29888
+	  Fixes format truncation warnings in gcc 12.2.1.
 
-	  Change-Id: Ib6171c25c9fe8e61cc8d11129e324c021bc30b62
+	  ASTERISK-30349 #close
 
-2022-01-26 07:56 +0000 [135e48deba]  George Joseph <gjoseph@digium.com>
+	  Change-Id: I42be4edf0284358b906e765d1966b6b9d66e1d3c
 
-	* build: Add "basebranch" to .gitreview
+2022-11-01 15:37 +0000 [5c114dcb4a]  Michael Kuron <m.kuron@gmx.de>
 
-	  If you have a development branch for a major project that
-	  will receive gerrit reviews it'll probably be named something
-	  like "development/16/newproject".  That will necessitate setting
-	  "defaultbranch=development/16/newproject" in .gitreview.  The
-	  make_version script uses that variable to construct the asterisk
-	  version however, which results in versions like
-	  "GIT-development/16/newproject-ee582a8c7b" which is probably not
-	  what you want.  Worse, since the download_externals script uses
-	  make_version to construct the URL to download the binary codecs
-	  or DPMA.  Since it's expecting a simple numeric version, the
-	  downloads will fail.
+	* manager: AOC-S support for AOCMessage
 
-	  To get this to work, a new variable "basebranch" has been added
-	  to .gitreview and make_version has been updated to use that instead
-	  of defaultversion:
+	  ASTERISK-21502
 
-	  .gitreview:
-	  defaultbranch=development/16/myproject
-	  basebranch=16
+	  Change-Id: I051b778f8c862d3b4794d28f2f3d782316707b08
 
-	  Now git-review will send the reviews to the proper branch
-	  (development/16/myproject) but the version will still be
-	  constructed using the simple branch number (16).
+2022-10-23 04:42 +0000 [fee9012fe1]  Michael Kuron <m.kuron@gmx.de>
 
-	  If "basebranch" is missing from .gitreview, make_version will
-	  fall back to using "defaultbranch".
+	* res_pjsip_aoc: New module for sending advice-of-charge with chan_pjsip
 
-	  Change-Id: I2941a3b21e668febeb6cfbc1a7bb51a67726fcc4
+	  chan_sip supported sending AOC-D and AOC-E information in SIP INFO
+	  messages in an "AOC" header in a format that was originally defined by
+	  Snom. In the meantime, ETSI TS 124 647 introduced an XML-based AOC
+	  format that is supported by devices from multiple vendors, including
+	  Snom phones with firmware >= 8.4.2 (released in 2010).
 
-2021-12-15 12:36 +0000 [6fc8453e96]  Naveen Albert <asterisk@phreaknet.org>
+	  This commit adds a new res_pjsip_aoc module that inserts AOC information
+	  into outgoing messages or sends SIP INFO messages as described below.
+	  It also fixes a small issue in res_pjsip_session which didn't always
+	  call session supplements on outgoing_response.
 
-	* cdr: allow disabling CDR by default on new channels
+	  * AOC-S in the 180/183/200 responses to an INVITE request
+	  * AOC-S in SIP INFO (if a 200 response has already been sent or if the
+	    INVITE was sent by Asterisk)
+	  * AOC-D in SIP INFO
+	  * AOC-D in the 200 response to a BYE request (if the client hangs up)
+	  * AOC-D in a BYE request (if Asterisk hangs up)
+	  * AOC-E in the 200 response to a BYE request (if the client hangs up)
+	  * AOC-E in a BYE request (if Asterisk hangs up)
 
-	  Adds a new option, defaultenabled, to the CDR core to
-	  control whether or not CDR is enabled on a newly created
-	  channel. This allows CDR to be disabled by default on
-	  new channels and require the user to explicitly enable
-	  CDR if desired. Existing behavior remains unchanged.
+	  The specification defines one more, AOC-S in an INVITE request, which
+	  is not implemented here because it is not currently possible in
+	  Asterisk to have AOC data ready at this point in call setup. Once
+	  specifying AOC-S via the dialplan or passing it through from another
+	  SIP channel's INVITE is possible, that might be added.
 
-	  ASTERISK-29808 #close
+	  The SIP INFO requests are sent out immediately when the AOC indication
+	  is received. The others are inserted into an appropriate outgoing
+	  message whenever that is ready to be sent. In the latter case, the XML
+	  is stored in a channel variable at the time the AOC indication is
+	  received. Depending on where the AOC indications are coming from (e.g.
+	  PRI or AMI), it may not always be possible to guarantee that the AOC-E
+	  is available in time for the BYE.
 
-	  Change-Id: Ibb78c11974bda229bbb7004b64761980e0b2c6d1
+	  Successfully tested AOC-D and both variants of AOC-E with a Snom D735
+	  running firmware 10.1.127.10. It does not appear to properly support
+	  AOC-S however, so that could only be tested by inspecting SIP traces.
 
-2022-01-11 13:19 +0000 [a4b01ececb]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-21502 #close
+	  Reported-by: Matt Jordan <mjordan@digium.com>
 
-	* res_tonedetect: Fixes some logic issues and typos
+	  Change-Id: Iebb7ad0d5f88526bc6629d3a1f9f11665434d333
 
-	  Fixes some minor logic issues with the module:
+2022-12-08 04:33 +0000 [564349ff5d]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  Previously, the OPT_END_FILTER flag was getting
-	  tested before options were parsed, so it could
-	  never evaluate to true (wrong ordering).
+	* ari: Destroy body variables in channel create.
 
-	  Additionally, the initially parsed timeout (float)
-	  needs to be compared with 0, not the result int
-	  which is set afterwards (wrong variable).
+	  When passing a JSON body to the 'create' channel route
+	  it would be converted into Asterisk variables, but never
+	  freed resulting in a memory leak.
 
-	  ASTERISK-29857 #close
+	  This change makes it so that the variables are freed in
+	  all cases.
 
-	  Change-Id: I0062bce3b391c15e5df7a714780eeaa96dd93d4c
+	  ASTERISK-30344
 
-2022-01-11 12:33 +0000 [5df5a70d37]  Naveen Albert <asterisk@phreaknet.org>
+	  Change-Id: I924dbd866a01c6073e2d6fb846ccaa27ef72d49d
 
-	* func_frame_drop: Fix typo referencing wrong buffer
+2022-11-03 15:28 +0000 [b9c031c1f8]  Naveen Albert <asterisk@phreaknet.org>
 
-	  In order to get around the issue of certain frames
-	  having names that could overlap, func_frame_drop
-	  surrounds names with commas for the purposes of
-	  comparison.
+	* app_voicemail: Fix missing email in msg_create_from_file.
 
-	  The buffer is allocated and printed to properly,
-	  but the original buffer is used for comparison.
-	  In most cases, this wouldn't have had any effect,
-	  but that was not the intention behind the buffer.
-	  This updates the code to reference the modified
-	  buffer instead.
+	  msg_create_from_file currently does not dispatch emails,
+	  which means that applications using this function, such
+	  as MixMonitor, will not trigger notifications to users
+	  (only AMI events are sent our currently). This is inconsistent
+	  with other ways users can receive voicemail.
 
-	  ASTERISK-29854 #close
+	  This is fixed by adding an option that attempts to send
+	  an email and falling back to just the notifications as
+	  done now if that fails. The existing behavior remains
+	  the default.
 
-	  Change-Id: I430b52e14e712d0e62a23aa3b5644fe958b684a7
+	  ASTERISK-30283 #close
 
-2022-01-20 06:56 +0000 [9c9083b45a]  Torrey Searle <tsearle@voxbone.com>
+	  Change-Id: I597cbb9cf971a18d8776172b26ab187dc096a5c7
 
-	* res/res_rtp_asterisk: fix skip in rtp sequence numbers after dtmf
+2022-11-25 03:59 +0000 [58534b309f]  Marcel Wagner <mwagner@sipgate.de>
 
-	  When generating dtmfs, asterisk can incorrectly think packet loss
-	  occured during the dtmf generation, resulting in a jump in sequence
-	  numbers when forwarding voice frames resumes.  This patch forces
-	  asterisk to re-learn the expected sequence number after each DTMF
-	  to avoid this
+	* res_pjsip: Fix typo in from_domain documentation
 
-	  ASTERISK-29869 #close
+	  This fixes a small typo in the from_domain documentation on the endpoint documentation
 
-	  Change-Id: Icc7de3d947b207b82c99d3c327af8095884df853
+	  ASTERISK-30328 #close
 
-2022-01-13 16:31 +0000 [98f86697cc]  Kevin Harwell <kharwell@sangoma.com>
+	  Change-Id: Ia6f0897c3f5cab899ef2cde6b3ac07265b8beb21
 
-	* res_http_websocket: Add a client connection timeout
+2022-11-21 12:53 +0000 [531eacd6c9]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Previously there was no way to specify a connection timeout when
-	  attempting to connect a websocket client to a server. This patch
-	  makes it possible to now do such.
+	* res_hep: Add support for named capture agents.
 
-	  Change-Id: I5812f6f28d3d13adbc246517f87af177fa20ee9d
+	  Adds support for the capture agent name field
+	  of the Homer protocol to Asterisk by allowing
+	  users to specify a name that will be sent to
+	  the HEP server.
 
-2022-01-21 10:34 +0000 [5b47b7a37e]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK-30322 #close
 
-	* build: Rebuild configure and autoconfig.h.in
+	  Change-Id: I6136583017f9dd08daeb8be02f60fb8df4639a2b
 
-	  autoconfigh.h.in was missed in the original review for this
-	  issue. Additionally it looks like I have newer pkg-config autoconf
-	  macros on my development machine.
+2021-06-28 11:56 +0000 [b365ea8601]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29817
+	* app_if: Adds conditional branch applications
 
-	  Change-Id: I3c85a4de82c5d7d6e0e23dad4c33bb650a86a57b
+	  Adds the If, ElseIf, Else, ExitIf, and EndIf
+	  applications for conditional execution
+	  of a block of dialplan, similar to the While,
+	  EndWhile, and ExitWhile applications. The
+	  appropriate branch is executed at most once
+	  if available and may be broken out of while
+	  inside.
 
-2021-12-08 15:14 +0000 [ac8988c9a3]  Mike Bradeen <mbradeen@sangoma.com>
+	  ASTERISK-29497
 
-	* sched: fix and test a double deref on delete of an executing call back
+	  Change-Id: I3aa3bd35a5add82465c6ee9bd86b64601f0e1f49
 
-	  sched: Avoid a double deref when AST_SCHED_DEL_UNREF is called on an
-	  executing call-back. This is done by adding a new variable 'rescheduled'
-	  to the struct sched which is set in ast_sched_runq and checked in
-	  ast_sched_del_nonrunning. ast_sched_del_nonrunning is a replacement for
-	  now deprecated ast_sched_del which returns a new possible value -2
-	  if called on an executing call-back with rescheduled set. ast_sched_del
-	  is modified to call ast_sched_del_nonrunning to maintain existing code.
-	  AST_SCHED_DEL_UNREF is also updated to look for the -2 in which case it
-	  will not throw a warning or invoke refcall.
-	  test_sched: Add a new unit test sched_test_freebird that will check the
-	  reference count in the resolved scenario.
+2022-10-16 19:33 +0000 [0d6003fa9a]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29698
+	* res_pjsip_session.c: Map empty extensions in INVITEs to s.
 
-	  Change-Id: Icfb16b3acbc29cf5b4cef74183f7531caaefe21d
+	  Some SIP devices use an empty extension for PLAR functionality.
 
-2022-01-19 16:33 +0000 [6e8bbe4b3a]  Luke Escude <luke@primevox.net>
+	  Rather than rejecting these empty extensions, we now use the s
+	  extension for such calls to mirror the existing PLAR functionality
+	  in Asterisk (e.g. chan_dahdi).
 
-	* res_pjsip_sdp_rtp.c: Support keepalive for video streams.
+	  ASTERISK-30265 #close
 
-	  ASTERISK-28890 #close
+	  Change-Id: I0861a405cd49bbbf532b52f7b47f0e2810832590
 
-	  Change-Id: Iad269a8dc36f892ede90fe8ceb3010560c0f70d1
+2022-11-17 13:30 +0000 [b83af13f65]  Marcel Wagner <mwagner@sipgate.de>
 
-2022-01-04 03:11 +0000 [c8d89e7e1b]  Mark Petersen <bugs.digium.com@zombie.dk>
+	* res_pjsip: Update contact_user to point out default
 
-	* app_queue.c: Queue don't play "thank-you" when here is no hold time announcements
+	  Updates the documentation for the 'contact_user' field to point out the
+	  default outbound contact if no contact_user is specified 's'
 
-	  if holdtime is (0 min, 0 sec) there is no hold time announcements
-	  we should then also not playing queue-thankyou
+	  ASTERISK-30316 #close
 
-	  ASTERISK-29831
+	  Change-Id: I61f24fb9164e4d07e05908a2511805281874c876
 
-	  Change-Id: Ic7e51dcde526b23f1cd8d24e1d1e2d81e10f9d2c
+2022-11-23 16:59 +0000 [80e6205bb0]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-11-10 22:24 +0000 [d68d90c5be]  Michał Górny <mgorny@NetBSD.org>
+	* res_adsi: Fix major regression caused by media format rearchitecture.
 
-	* main: Enable rdtsc support on NetBSD
+	  The commit that rearchitected media formats,
+	  a2c912e9972c91973ea66902d217746133f96026 (ASTERISK_23114)
+	  introduced a regression by improperly translating code in res_adsi.c.
+	  In particular, the pointer to the frame buffer was initialized
+	  at the top of adsi_careful_send, rather than dynamically updating it
+	  for each frame, as is required.
 
-	  Enable the Linux rdtsc implementation on NetBSD as well.  The assembly
-	  works correctly there.
+	  This resulted in the first frame being repeatedly sent,
+	  rather than advancing through the frames.
+	  This corrupted the transmission of the CAS to the CPE,
+	  which meant that CPE would never respond with the DTMF acknowledgment,
+	  effectively completely breaking ADSI functionality.
 
-	  ASTERISK-29851
+	  This issue is now fixed, and ADSI now works properly again.
 
-	  Change-Id: I460ad9b4d971913420ecb84186f5ba5ab03f6f37
+	  ASTERISK-29793 #close
 
-2021-11-10 21:40 +0000 [90d02cf0a3]  Michał Górny <mgorny@NetBSD.org>
+	  Change-Id: Icdeddf733eda2981c98712d1ac9cddc0db507dbe
 
-	* build_tools/make_version: Fix sed(1) syntax compatibility with NetBSD
+2022-07-21 14:07 +0000 [406143ae61]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Fix the sed(1) invocation used to process git-svn-id not to use "\s"
-	  that is a GNU-ism and is not supported by NetBSD sed.  As a result,
-	  this call did not work properly and make_version did output the full
-	  git-svn-id line rather than the revision.
+	* res_pjsip_header_funcs: Add custom parameter support.
 
-	  ASTERISK-29852
+	  Adds support for custom URI and header parameters
+	  in the From header in PJSIP. Parameters can be
+	  both set and read using this function.
 
-	  Change-Id: Ie4b406e2748920643446851a0a252a4ca7245772
+	  ASTERISK-30150 #close
 
-2021-11-10 22:29 +0000 [c8ef232d76]  Michał Górny <mgorny@NetBSD.org>
+	  Change-Id: Ifb1bc3c512ad5f6faeaebd7817f004a2ecbd6428
 
-	* main/utils: Implement ast_get_tid() for NetBSD
+2022-11-13 16:15 +0000 [83eb113e0f]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Implement the ast_get_tid() function for NetBSD system.  NetBSD supports
-	  getting the TID via _lwp_self().
+	* func_presencestate: Fix invalid memory access.
 
-	  ASTERISK-29850
+	  When parsing information from AstDB while loading,
+	  it is possible that certain pointers are never
+	  set, which leads to invalid memory access and
+	  then, fatally, invalid free attempts on this memory.
+	  We now initialize to NULL to prevent this.
 
-	  Change-Id: If57fd3f9ea15ef5d010bfbdcbbbae9b379f72f8c
+	  ASTERISK-30311 #close
 
-2021-11-10 20:05 +0000 [7b1e5fa34a]  Michał Górny <mgorny@NetBSD.org>
+	  Change-Id: I6120681d04fd2c12a9473f35ce95a1f8e74e3929
 
-	* BuildSystem: Fix misdetection of gethostbyname_r() on NetBSD
+2022-12-01 05:54 +0000 [b90e57758b]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Fix the configure script not to detect the presence of gethostbyname_r()
-	  on NetBSD incorrectly.  NetBSD includes it as an internal libc symbol
-	  that is not exposed in system headers and that is incompatible with
-	  other implementations.  In order to avoid misdetecting it, perform
-	  the symbol check only if the declaration is found in the public header
-	  first.
+	* sig_analog: Fix no timeout duration.
 
-	  ASTERISK-29817
+	  ASTERISK_28702 previously attempted to fix an
+	  issue with flash hook hold timing out after
+	  just under 17 minutes, when it should have never
+	  been timing out. It fixed this by changing 999999
+	  to INT_MAX, but it did so in chan_dahdi, which
+	  is the wrong place since ss_thread is now in
+	  sig_analog and the one in chan_dahdi is mostly
+	  dead code.
 
-	  Change-Id: Iafa359b09908251bcd299ff54be003ea129b9eda
+	  This fixes this by porting the fix to sig_analog.
 
-2021-11-10 22:06 +0000 [eef29d24e1]  Michał Górny <mgorny@NetBSD.org>
+	  ASTERISK-30336 #close
 
-	* include: Remove unimplemented HMAC declarations
+	  Change-Id: I05eb69cc0b5319d357842a70bd26ef64d145cb15
 
-	  Remove the HMAC declarations from the includes.  They are
-	  not implemented nor used anywhere, and their presence breaks the build
-	  on NetBSD that delivers an incompatible hmac() function in <stdlib.h>.
+2022-11-05 07:11 +0000 [52c7d3ed07]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29818
+	* xmldoc: Allow XML docs to be reloaded.
 
-	  Change-Id: I0c4b88645e30174b1b63846a6b328625b69c2ea7
+	  The XML docs are currently only loaded on
+	  startup with no way to update them during runtime.
+	  This makes it impossible to load modules that
+	  use ACO/Sorcery (which require documentation)
+	  if they are added to the source tree and built while
+	  Asterisk is running (e.g. external modules).
 
-2022-01-11 12:41 +0000 [18c257b44d]  Naveen Albert <asterisk@phreaknet.org>
+	  This adds a CLI command to reload the XML docs
+	  during runtime so that documentation can be updated
+	  without a full restart of Asterisk.
 
-	* frame.h: Fix spelling typo
+	  ASTERISK-30289 #close
 
-	  Fixes CNG description from "noice" to "noise".
+	  Change-Id: I4f265b0e5517e757c5453a0f241201a5788d3a07
 
-	  ASTERISK-29855 #close
+2022-11-24 09:56 +0000 [a4bcdce1db]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Ie7cbbd7d72b426693df7447384ff8700318cd36d
+	* rtp_engine.h: Update examples using ast_format_set.
 
-2022-01-11 12:46 +0000 [a9e9e15c3a]  Naveen Albert <asterisk@phreaknet.org>
+	  This file includes some doxygen comments referencing
+	  ast_format_set. This is an obsolete API that was
+	  removed years back, but documentation was not fully
+	  updated to reflect that. These examples are
+	  updated to the current way of doing things
+	  (using the format cache).
 
-	* res_rtp_asterisk: Fix typo in flag test/set
+	  ASTERISK-30327 #close
 
-	  The code currently checks to see if an RFC3389
-	  warning flag is set, except if it is, it merely
-	  sets the flag again, the logic of which doesn't
-	  make any sense.
+	  Change-Id: I570f3b8007fa17ba470cc7117f44bfe7c555d2f7
 
-	  This adjusts the if comparison to check if the
-	  flag has NOT been set, and if so, emit a notice
-	  log event and set the flag so that future frames
-	  do not cause an event to be logged.
+2022-11-04 06:04 +0000 [691178c48e]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29856 #close
+	* app_mixmonitor: Add option to use real Caller ID for voicemail.
 
-	  Change-Id: Ib7098c947c63537d087a03b4646199fbb963f8e1
+	  MixMonitor currently uses the Connected Line as the Caller ID
+	  for voicemails. This is due to the implementation being written
+	  this way for use with Digium phones. However, in general this
+	  is not correct for generic usage in the dialplan, and people
+	  may need the real Caller ID instead. This adds an option to do that.
 
-2022-01-18 08:04 +0000 [cc38ed9c21]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-30286 #close
 
-	* bundled_pjproject: Fix srtp detection
+	  Change-Id: I3d0ce76dfe75e2a614e0f709ab27acbd2478267c
 
-	  Reverted recent change that set '--with-external-srtp' instead
-	  of '--without-external-srtp'.  Since Asterisk handles all SRTP,
-	  we don't need it enabled in pjproject at all.
+2022-11-29 14:02 +0000 [d476994768]  Ben Ford <bford@digium.com>
 
-	  ASTERISK-29867
+	* pjproject: 2.13 security fixes
 
-	  Change-Id: I2ce1bdd30abd21c062eac8f8fefe9b898787b801
+	  Backports two security fixes (c4d3498 and 450baca) from pjproject 2.13.
 
-2022-01-10 07:44 +0000 [f55886a72c]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-30338
 
-	* res_pjsip: Make message_filter and session multipart aware
+	  Change-Id: I86fdc003d5d22cb66e7cc6dc3313a8194f27eb69
 
-	  Neither pjsip_message_filter's filter_on_tx_message() nor
-	  res_pjsip_session's session_outgoing_nat_hook() were multipart
-	  aware and just assumed that an SDP would be the only thing in
-	  a message body.  Both were changed to use the new
-	  pjsip_get_sdp_info() function which searches for an sdp in
-	  both single- and multi- part message bodies.
+2022-10-10 09:35 +0000 [7684c9e907]  George Joseph <gjoseph@digium.com>
 
-	  ASTERISK-29813
+	* pjsip_transport_events: Fix possible use after free on transport
 
-	  Change-Id: I8f5b8cfdc27f1d4bd3e7491ea9090951a4525c56
+	  It was possible for a module that registered for transport monitor
+	  events to pass in a pjsip_transport that had already been freed.
+	  This caused pjsip_transport_events to crash when looking up the
+	  monitor for the transport.  The fix is a two pronged approach.
 
-2022-01-06 13:05 +0000 [59cf9f0047]  George Joseph <gjoseph@digium.com>
+	  1. We now increment the reference count on pjsip_transports when we
+	  create monitors for them, then decrement the count when the
+	  transport is going to be destroyed.
 
-	* res_pjsip: Add utils for checking media types
+	  2. There are now APIs to register and unregister monitor callbacks
+	  by "transport key" which is a string concatenation of the remote ip
+	  address and port.  This way the module needing to monitor the
+	  transport doesn't have to hold on to the transport object itself to
+	  unregister.  It just has to save the transport_key.
 
-	  Added two new functions to assist checking media types...
+	  * Added the pjsip_transport reference increment and decrement.
 
-	  * ast_sip_are_media_types_equal compares two pjsip_media_types.
-	  * ast_sip_is_media_type_in tests if one media type is in a list
-	    of others.
+	  * Changed the internal transport monitor container key from the
+	    transport->obj_name (which may not be unique anyway) to the
+	    transport_key.
 
-	  Added static definitions for commonly used media types to
-	  res_pjsip.h.
+	  * Added a helper macro AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR() that
+	    fills a buffer with the transport_key using a passed-in
+	    pjsip_transport.
 
-	  Changed several modules to use the new functions and static
-	  definitions.
+	  * Added the following functions:
+	    ast_sip_transport_monitor_register_key
+	    ast_sip_transport_monitor_register_replace_key
+	    ast_sip_transport_monitor_unregister_key
+	    and marked their non-key counterparts as deprecated.
 
-	  ASTERISK_29813
-	  (not ready to close)
+	  * Updated res_pjsip_pubsub and res_pjsip_outbound_register to use
+	    the new "key" monitor functions.
 
-	  Change-Id: Ief77675235bd3bf00a6b095d4673fd878d0801b9
+	  NOTE: res_pjsip_registrar also uses the transport monitor
+	  functionality but doesn't have a persistent object other than
+	  contact to store a transport key.  At this time, it continues to
+	  use the non-key monitor functions.
 
-2022-01-12 11:12 +0000 [b59bd3d3e4]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-30244
 
-	* build: Fix issues building pjproject
+	  Change-Id: I1a20baf2a8643c272dcf819871d6c395f148f00b
 
-	  The change to allow easier hacking on bundled pjproject created
-	  a few issues:
+2022-10-03 13:54 +0000 [81f10e847e]  Mike Bradeen <mbradeen@sangoma.com>
 
-	  * The new Makefile was trying to run the bundled make even if
-	    PJPROJECT_BUNDLED=no.  third-party/Makefile now checks for
-	    PJPROJECT_BUNDLED and JANSSON_BUNDLED and skips them if they
-	    are "no".
+	* manager: prevent file access outside of config dir
 
-	  * When building with bundled, config_site.h was being copied
-	    only if a full make or a "make main" was done.  A "make res"
-	    would fail all the pjsip modules because they couldn't find
-	    config_site.h.  The Makefile now copies config_site.h and
-	    asterisk_malloc_debug.h into the pjproject source tree
-	    when it's "configure" is performed.  This is how it used
-	    to be before the big change.
+	  Add live_dangerously flag to manager and use this flag to
+	  determine if a configuation file outside of AST_CONFIG_DIR
+	  should be read.
 
-	  ASTERISK-29858
+	  ASTERISK-30176
 
-	  Change-Id: I9427264fa3cb8b3f59a95e5f9693eac236a6f76d
+	  Change-Id: I46b26af4047433b49ae5c8a85cb8cda806a07404
 
-2022-01-12 07:16 +0000 [f10947ecc2]  George Joseph <gjoseph@digium.com>
+2022-06-06 18:11 +0000 [eb1d7ab53c]  Mike Bradeen <mbradeen@sangoma.com>
 
-	* bundled_pjproject: Create generic pjsip_hdr_find functions
+	* ooh323c: not checking for IE minimum length
 
-	  pjsip_msg_find_hdr(), pjsip_msg_find_hdr_by_name(), and
-	  pjsip_msg_find_hdr_by_names() require a pjsip_msg to be passed in
-	  so if you need to search a header list that's not in a pjsip_msg,
-	  you have to do it yourself.  This commit adds generic versions of
-	  those 3 functions that take in the actual header list head instead
-	  of a pjsip_msg so if you need to search a list of headers in
-	  something like a pjsip_multipart_part, you can do so easily.
+	  When decoding q.931 encoded calling/called number
+	  now checking for length being less than minimum required.
 
-	  Change-Id: I6f2c127170eafda48e5e0d5d4d187bcd52b4df07
+	  ASTERISK-30103
 
-2022-01-12 13:20 +0000 [3fd47840c9]  Sean Bright <sean.bright@gmail.com>
+	  Change-Id: I3dcfce0f35eca258dc450f87c92d4d7af402c2e7
 
-	* say.c: Prevent erroneous failures with 'say' family of functions.
+2022-11-11 14:30 +0000 [c7df5ee7c1]  Naveen Albert <asterisk@phreaknet.org>
 
-	  A regression was introduced in ASTERISK~29531 that caused 'say'
-	  functions to fail with file lists that would previously have
-	  succeeded. This caused affected channels to hang up where previously
-	  they would have continued.
+	* pbx_builtins: Allow Answer to return immediately.
 
-	  We now explicitly check for the empty string to restore the previous
-	  behavior.
+	  The Answer application currently waits for up to 500ms
+	  for media, even if users specify a different timeout.
 
-	  ASTERISK-29859 #close
+	  This adds an option to not wait for media on the channel
+	  by doing a raw answer instead. The default 500ms threshold
+	  is also documented.
 
-	  Change-Id: Ia2e5769868e2792313c2d7c07996efe009c6f8d5
+	  ASTERISK-30308 #close
 
-2022-01-08 09:09 +0000 [e006d2d2a6]  Naveen Albert <asterisk@phreaknet.org>
+	  Change-Id: Id59cd340c44b8b8b2384c479e17e5123e917cba4
 
-	* pbx_variables: add missing ASTSBINDIR variable
+2022-11-10 18:47 +0000 [5ede4e217a]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Every config variable in the directories
-	  section of asterisk.conf currently has a
-	  counterpart built-in variable containing
-	  the value of the config option, except
-	  for the last one, astsbindir, which should
-	  have an ASTSBINDIR variable.
+	* chan_dahdi: Allow FXO channels to start immediately.
 
-	  However, the actual corresponding ASTSBINDIR
-	  variable is missing in pbx_variables.c.
+	  Currently, chan_dahdi will wait for at least one
+	  ring before an incoming call can enter the dialplan.
+	  This is generally necessary in order to receive
+	  the Caller ID spill and/or distinctive ringing
+	  detection.
 
-	  This adds the missing variable so that all
-	  the config options have their corresponding
-	  variable.
+	  However, if neither of these is required, then there
+	  is nothing gained by waiting for one ring and this
+	  unnecessarily delays call setup. Users can now
+	  use immediate=yes to make FXO channels (FXS signaled)
+	  begin processing dialplan as soon as Asterisk receives
+	  the call.
 
-	  ASTERISK-29847 #close
+	  ASTERISK-30305 #close
 
-	  Change-Id: I36006faf471825b36ebc8aa5e87a3bcb38d446fc
+	  Change-Id: I20818b370b2e4892c7f40c8a8753fa06a81750b5
 
-2022-01-08 14:35 +0000 [707f32170c]  Naveen Albert <asterisk@phreaknet.org>
+2022-09-07 07:06 +0000 [60b81eabe0]  Maximilian Fridrich <m.fridrich@commend.com>
 
-	* documentation: Document built-in system and channel vars
+	* core & res_pjsip: Improve topology change handling.
 
-	  Documentation for built-in special system and channel
-	  vars is currently outdated, and updating is a manual
-	  process since there is no XML documentation for these
-	  anywhere.
+	  This PR contains two relatively separate changes in channel.c and
+	  res_pjsip_session.c which ensure that topology changes are not ignored
+	  in cases where they should be handled.
 
-	  This adds documentation for system vars to func_env
-	  and for channel vars to func_channel so that they
-	  appear along with the corresponding fields that would
-	  be accessed using a function.
+	  For channel.c:
 
-	  ASTERISK-29848 #close
+	  The function ast_channel_request_stream_topology_change only triggers a
+	  stream topology request change indication, if the channel's topology
+	  does not equal the requested topology. However, a channel could be in a
+	  state where it is currently "negotiating" a new topology but hasn't
+	  updated it yet, so the topology request change would be lost. Channels
+	  need to be able to handle such situations internally and stream
+	  topology requests should therefore always be passed on.
 
-	  Change-Id: I6997f925c4a45fffe71321861f5898a8b7182fa9
+	  In the case of chan_pjsip for example, it queues a session refresh
+	  (re-INVITE) if it is currently in the middle of a transaction or has
+	  pending requests (among other reasons).
 
-2021-11-30 16:35 +0000 [3f093b8dda]  George Joseph <gjoseph@digium.com>
+	  Now, ast_channel_request_stream_topology_change always indicates a
+	  stream topology request change even if the requested topology equals the
+	  channel's topology.
 
-	* bundled_pjproject:  Make it easier to hack
+	  For res_pjsip_session.c:
 
-	  There are times when you need to troubleshoot issues with bundled
-	  pjproject or add new features that need to be pushed upstream
-	  but...
+	  The function resolve_refresh_media_states does not process stream state
+	  changes if the delayed active state differs from the current active
+	  state. I.e. if the currently active stream state has changed between the
+	  time the sip session refresh request was queued and the time it is being
+	  processed, the session refresh is ignored. However, res_pjsip_session
+	  contains logic that ensures that session refreshes are queued and
+	  re-queued correctly if a session refresh is currently not possible. So
+	  this check is not necessary and led to some session refreshes being
+	  lost.
 
-	  * The source directory created by extracting the pjproject tarball
-	    is not scanned for code changes so you have to keep forcing
-	    rebuilds.
-	  * The source directory isn't a git repo so you can't easily create
-	    patches, do git bisects, etc.
-	  * Accidentally doing a make distclean will ruin your day by wiping
-	    out the source directory, and your changes.
-	  * etc.
+	  Now, a session refresh is done even if the delayed active state differs
+	  from the current active state and it is checked whether the delayed
+	  pending state differs from the current active - because that means a
+	  refresh is necessary.
 
-	  This commit makes that easier.
-	  See third-party/pjproject/README-hacking.md for the details.
+	  Further, the unit test of resolve_refresh_media_states was adapted to
+	  reflect the new behavior. I.e. the changes to delayed pending are
+	  prioritized over the changes to current active because we want to
+	  preserve the original intention of the pending state.
 
-	  ASTERISK-29824
+	  ASTERISK-30184
 
-	  Change-Id: Idb1251040affdab31d27cd272dda68676da9b268
+	  Change-Id: Icd0703295271089057717006730b555b9a1d4e5a
 
-2021-12-24 10:26 +0000 [0bbef4d8c5]  Sean Bright <sean.bright@gmail.com>
+2022-09-24 05:15 +0000 [2efa290d3c]  Naveen Albert <asterisk@phreaknet.org>
 
-	* utils.c: Remove all usages of ast_gethostbyname()
+	* sla: Prevent deadlock and crash due to autoservicing.
 
-	  gethostbyname() and gethostbyname_r() are deprecated in favor of
-	  getaddrinfo() which we use in the ast_sockaddr family of functions.
+	  SLAStation currently autoservices the station channel before
+	  creating a thread to actually dial the trunk. This leads
+	  to duplicate servicing of the channel which causes assertions,
+	  deadlocks, crashes, and moreover not the correct behavior.
 
-	  ASTERISK-29819 #close
+	  Removing the autoservice prevents the crash, but if the station
+	  hangs up before the trunk answers, the call hangs since the hangup
+	  was never serviced on the channel.
 
-	  Change-Id: Ie277c0ef768d753b169c121ef570a71665692ab7
+	  This is fixed by not autoservicing the channel, but instead
+	  servicing it in the thread dialing the trunk, since it is doing
+	  so synchronously to begin with. Instead of sleeping for 100ms
+	  in a loop, we simply use the channel for timing, and abort
+	  if it disappears.
 
-2021-12-13 09:53 +0000 [54f2f1e027]  Naveen Albert <asterisk@phreaknet.org>
+	  The same issue also occurs with SLATrunk when a call is answered,
+	  because ast_answer invokes ast_waitfor_nandfds. Thus, we use
+	  ast_raw_answer instead which does not cause any conflict and allows
+	  the call to be answered normally without thread blocking issues.
 
-	* say.conf: fix 12pm noon logic
+	  ASTERISK-29998 #close
 
-	  Fixes 12pm noon incorrectly returning 0/a.m.
-	  Also fixes a misspelling typo in the config.
+	  Change-Id: Icc237d50354b5910000d2305901e86d2c87bb9d8
 
-	  ASTERISK-29695 #close
+2022-11-07 09:30 +0000 [ce2153fc5a]  Jaco Kroon <jaco@uls.co.za>
 
-	  Change-Id: Ie40f9618636eb4c483b449bd707a5dcffca5c406
+	* Build system: Avoid executable stack.
 
-2022-01-04 08:08 +0000 [ee69441fbd]  Sean Bright <sean.bright@gmail.com>
+	  Found in res_geolocation, but I believe others may have similar issues,
+	  thus not linking to a specific issue.
 
-	* pjproject: Fix incorrect unescaping of tokens during parsing
+	  Essentially gcc doesn't mark the stack for being non-executable unless
+	  it's compiling the source, this informs ld via gcc to mark the object as
+	  not requiring an executable stack (which a binary blob obviously
+	  doesn't).
 
-	  ASTERISK-29664 #close
+	  ASTERISK-30321
 
-	  Change-Id: I29dcde52e9faeaf2609c604eada61c6a9e49d8f5
+	  Change-Id: I71bcc2fd1fe0c82a28b3257405d6f2b566fd9bfc
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
 
-2021-12-30 07:02 +0000 [dea71ddbbf]  Mark Petersen <bugs.digium.com@zombie.dk>
+2022-11-10 06:11 +0000 [002afc3f2a]  Naveen Albert <asterisk@phreaknet.org>
 
-	* app_queue.c: Support for Nordic syntax in announcements
+	* func_json: Fix memory leak.
 
-	  adding support for playing the correct en/et for nordic languages
-	  by adding 'n' for neuter gender in the relevant ast_say_number
+	  A memory leak was present in func_json due to
+	  using ast_json_free, which just calls ast_free,
+	  as opposed to recursively freeing the JSON
+	  object as needed. This is now fixed to use the
+	  right free functions.
 
-	  ASTERISK-29827
+	  ASTERISK-30293 #close
 
-	  Change-Id: I03ebc827d2f0dc95132ab2f42799893c70edc5b1
+	  Change-Id: I982324dde841dc9147c8d8ad35c8719daf418b49
 
-2021-12-23 08:50 +0000 [6d7161820e]  Naveen Albert <asterisk@phreaknet.org>
+2022-11-10 06:20 +0000 [1e77b8c473]  Naveen Albert <asterisk@phreaknet.org>
 
-	* dsp: Add define macro for DTMF_MATRIX_SIZE
+	* test_json: Remove duplicated static function.
 
-	  Adds the macro DTMF_MATRIX_SIZE to replace
-	  the magic number 4 sprinkled throughout
-	  dsp.c.
+	  Removes the function mkstemp_file and uses
+	  ast_file_mkftemp from file.h instead.
 
-	  ASTERISK-29815 #close
+	  ASTERISK-30295 #close
 
-	  Change-Id: Ie3bddb92c6b16204ece0f758009e9490eb33b9ba
+	  Change-Id: I7412ec06f88c39ee353bcdb8c976c2fcac546609
 
-2022-01-03 11:10 +0000 [f133ae6ca2]  Naveen Albert <asterisk@phreaknet.org>
+2022-11-16 05:40 +0000 [61922d2934]  Joshua C. Colp <jcolp@sangoma.com>
 
-	* ami: Add AMI event for Wink
+	* res_agi: Respect "transmit_silence" option for "RECORD FILE".
 
-	  Adds an AMI event for a wink frame.
+	  The "RECORD FILE" command in res_agi has its own
+	  implementation for actually doing the recording. This
+	  has resulted in it not actually obeying the option
+	  "transmit_silence" when recording.
 
-	  ASTERISK-29830 #close
+	  This change causes it to now send silence if the
+	  option is enabled.
 
-	  Change-Id: I83e426de5e37baed79a4dbcc91e9e8d030ef1b56
+	  ASTERISK-30314
 
-2021-12-15 08:23 +0000 [1c2f311ba3]  Naveen Albert <asterisk@phreaknet.org>
+	  Change-Id: Ib3a85601ff35d1b904f836691bad8a4b7e957174
 
-	* cli: Add module refresh command
+2022-11-03 15:56 +0000 [6e59b01e1a]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Adds a command to the CLI to unload and then
-	  load a module. This makes it easier to perform
-	  these operations which are often done
-	  subsequently to load a new version of a module.
+	* app_mixmonitor: Add option to delete files on exit.
 
-	  "module reload" already refers to reloading of
-	  configuration, so the name "refresh" is chosen
-	  instead.
+	  Adds an option that allows MixMonitor to delete
+	  its copy of any recording files before exiting.
 
-	  ASTERISK-29807 #close
+	  This can be handy in conjunction with options
+	  like m, which copy the file elsewhere, and the
+	  original files may no longer be needed.
 
-	  Change-Id: I595f6f11774a0de2565a1fba38da22309ce93a2c
+	  ASTERISK-30284 #close
 
-2022-01-02 19:13 +0000 [775c371d09]  Naveen Albert <asterisk@phreaknet.org>
+	  Change-Id: Ida093679c67e300efc154a97b6d8ec0f104e581e
 
-	* app_mp3: Throw warning on nonexistent stream
+2022-11-03 17:01 +0000 [49cfdbbdff]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Currently, the MP3Player application doesn't
-	  emit a warning if attempting to play a stream
-	  which no longer exists. This can be a common
-	  scenario as many mp3 streams are valid at some
-	  point but can disappear at any time.
+	* manager: Update ModuleCheck documentation.
 
-	  Now a warning is thrown if attempting to play
-	  a nonexistent MP3 stream, instead of silently
-	  exiting.
+	  The ModuleCheck XML documentation falsely
+	  claims that the module's version number is returned.
+	  This has not been the case since 14, since the version
+	  number is not available anymore, but the documentation
+	  was not changed at the time. It is now updated to
+	  reflect this.
 
-	  ASTERISK-29829 #close
+	  ASTERISK-30285 #close
 
-	  Change-Id: I53a0bf1ed1740166655eb66fe7675f6f808bf535
+	  Change-Id: Idde2d1205a11f2623fa1ddab192faa3dc4081e91
 
-2021-12-13 08:29 +0000 [b37feb42ae]  Naveen Albert <asterisk@phreaknet.org>
+2022-11-06 10:39 +0000 [8142b313c3]  Naveen Albert <asterisk@phreaknet.org>
 
-	* documentation: Add missing AMI documentation
+	* file.c: Don't emit warnings on winks.
 
-	  Adds missing documentation for some channel,
-	  bridge, and queue events.
+	  Adds an ignore case for wink since it should
+	  pass through with no warning.
 
-	  ASTERISK-24427
-	  ASTERISK-29515
+	  ASTERISK-30290 #close
 
-	  Change-Id: I92b06b88c8cadc0155f95ebe3e870b3e795a8c64
+	  Change-Id: Ieb7e34daa717357ac5c93efb0059f6c2321f16ad
 
-2021-11-15 16:13 +0000 [06f9227ac5]  Kevin Harwell <kharwell@sangoma.com>
+2022-11-02 09:24 +0000 [0c1c623dee]  George Joseph <gjoseph@digium.com>
 
-	* tcptls.c: refactor client connection to be more robust
+	* runUnittests.sh:  Save coredumps to proper directory
 
-	  The current TCP client connect code, blocks and does not handle EINTR
-	  error case.
+	  Fixed the specification of "outputdir" when calling ast_coredumper
+	  so the txt files are saved in the correct place.
 
-	  This patch makes the client socket non-blocking while connecting,
-	  ensures a connect does not immediately fail due to EINTR "errors",
-	  and adds a connect timeout option.
+	  ASTERISK-30282
 
-	  The original client start call sets the new timeout option to
-	  "infinite", thus making sure old, orginal behavior is retained.
+	  Change-Id: Ic631cb90c1e4c29d970c982dff45fda5e0eb15b6
 
-	  ASTERISK-29746 #close
+2022-10-01 14:49 +0000 [dfe2f38642]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I907571843a83e43c0742b95a64785f4411f02671
+	* app_stack: Print proper exit location for PBXless channels.
 
-2021-12-13 10:59 +0000 [dd6df42534]  Naveen Albert <asterisk@phreaknet.org>
+	  When gosub is executed on channels without a PBX, the context,
+	  extension, and priority are initialized to the channel driver's
+	  default location for that endpoint. As a result, the last Return
+	  will restore this location and the Gosub logs will print out bogus
+	  information about our exit point.
 
-	* app_sf: Add full tech-agnostic SF support
+	  To fix this, on channels that don't have a PBX, the execution
+	  location is left intact on the last return if there are no
+	  further stack frames left. This allows the correct location
+	  to be printed out to the user, rather than the bogus default
+	  context.
 
-	  Adds tech-agnostic support for SF signaling
-	  by adding SF sender and receiver applications
-	  as well as Dial integration.
+	  ASTERISK-30076 #close
 
-	  ASTERISK-29802 #close
+	  Change-Id: I1d42a99c9aa9e3708d32718863175158a894e414
 
-	  Change-Id: I7ec50752e9a661af639425e5d1e339f17411bcad
+2022-11-02 07:41 +0000 [f723b465e5]  George Joseph <gjoseph@digium.com>
 
-2021-12-15 06:23 +0000 [16a63027c0]  Steve Davies <steve@one47.co.uk>
+	* chan_rtp: Make usage of ast_rtp_instance_get_local_address clearer
 
-	* app_queue: Fix hint updates, allow dup. hints
+	  unicast_rtp_request() was setting the channel variables like this:
 
-	  A previous patch for ASTERISK_29578 caused a 'leak' of
-	  extension state information across queues, causing the
-	  state of the first member of unrelated queues to be
-	  updated in addition to the correct member. Which queues
-	  and members depended on the order of queues in the
-	  iterator.
+	  pbx_builtin_setvar_helper(chan, "UNICASTRTP_LOCAL_ADDRESS",
+	      ast_sockaddr_stringify_addr(&local_address));
+	  ast_rtp_instance_get_local_address(instance, &local_address);
+	  pbx_builtin_setvar_helper(chan, "UNICASTRTP_LOCAL_PORT",
+	      ast_sockaddr_stringify_port(&local_address));
 
-	  Additionally, it is possible to use the same 'hint:' on
-	  multiple queue members, so the update cannot break out
-	  of the update loop early when a match is found.
+	  ...which made it appear that UNICASTRTP_LOCAL_ADDRESS was being
+	  set before local_address was set.  In fact, the address part of
+	  local_address was set earlier in the function, just not the port.
+	  This was confusing however so ast_rtp_instance_get_local_address()
+	  is now being called before setting UNICASTRTP_LOCAL_ADDRESS.
 
-	  ASTERISK-29806 #close
+	  ASTERISK-30281
 
-	  Change-Id: If2c1d1cc2a752afd9286d79710fc818596e7a7ad
+	  Change-Id: I872ac49477100f4eb33891d46efc6ca21ec81aa4
 
-2021-12-23 15:57 +0000 [4fe94bab09]  Sean Bright <sean.bright@gmail.com>
+2022-10-13 11:19 +0000 [50e2921a48]  Mike Bradeen <mbradeen@sangoma.com>
 
-	* say.c: Honor requests for DTMF interruption.
+	* res_pjsip: prevent crash on websocket disconnect
 
-	  SayAlpha, SayAlphaCase, SayDigits, SayMoney, SayNumber, SayOrdinal,
-	  and SayPhonetic all claim to allow DTMF interruption if the
-	  SAY_DTMF_INTERRUPT channel variable is set to a truthy value, but we
-	  are failing to break out of a given 'say' application if DTMF actually
-	  occurs.
+	  When a websocket (or potentially any stateful connection) is quickly
+	  created then destroyed, it is possible that the qualify thread will
+	  destroy the transaction before the initialzing thread is finished
+	  with it.
 
-	  ASTERISK-29816 #close
+	  Depending on the timing, this can cause an assertion within pjsip.
 
-	  Change-Id: I6a96e0130560831d2cb45164919862b9bcb6287e
+	  To prevent this, ast_send_stateful_response will now create the group
+	  lock and add a reference to it before creating the transaction.
 
-2021-11-16 06:32 +0000 [4e204db2bf]  Florentin Mayer <f.mayer@commend.com>
+	  While this should resolve the crash, there is still the potential that
+	  the contact will not be cleaned up properly, see:ASTERISK~29286. As a
+	  result, the contact has to 'time out' before it will be removed.
 
-	* res_pjsip_sdp_rtp: Preserve order of RTP codecs
+	  ASTERISK-28689
 
-	  The ast_rtp_codecs_payloads functions do not preserve the order in which
-	  the payloads were specified on an incoming SDP media line. This leads to
-	  a problem with the codec negotiation functionality, as the format
-	  capabilities of the stream are extracted from the ast_rtp_codecs. This
-	  commit moves the ast_rtp_codec to ast_format conversion to the place
-	  where the order is still known.
+	  Change-Id: Id050fded2247a04d8f0fc5b8a2cf3e5482cb8cee
 
-	  ASTERISK-28863
-	  ASTERISK-29320
+2022-10-27 06:32 +0000 [afd86b47c1]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I3aabcfed3f379c36654f59c1872c313d0cb57e25
+	* tcptls: Prevent crash when freeing OpenSSL errors.
 
-2021-12-27 07:28 +0000 [d83a46869e]  Joshua C. Colp <jcolp@sangoma.com>
+	  write_openssl_error_to_log has been erroneously
+	  using ast_free instead of free, which will
+	  cause a crash when MALLOC_DEBUG is enabled since
+	  the memory was not allocated by Asterisk's memory
+	  manager. This changes it to use the actual free
+	  function directly to avoid this.
 
-	* bridge: Unlock channel during Local peer check.
+	  ASTERISK-30278 #close
 
-	  It's not safe to keep the channel locked while locking
-	  the peer Local channel, as it can result in a deadlock.
+	  Change-Id: Iac8b6468b718075809c45d8ad16b101af21a474d
 
-	  This change unlocks it during this time but keeps the
-	  bridge locked to ensure nothing changes about the bridge.
+2022-09-09 12:20 +0000 [096529d33f]  Igor Goncharovsky <igor.goncharovsky@gmail.com>
 
-	  ASTERISK-29821
+	* res_pjsip_outbound_registration: Allow to use multiple proxies for registration
 
-	  Change-Id: Ib68eb7037e5a479bcc2aceee77337cdde1fbdde6
+	  Current registration code use pjsip_parse_uri to verify outbound_proxy
+	  that is different from the reading this option for the endpoint. This
+	  made value with multiple proxies invalid for registration pjsip settings.
+	  Removing URI validation helps to use registration through multiple proxies.
 
-2021-11-07 09:32 +0000 [a5cdee36a7]  Josh Soref <jsoref@gmail.com>
+	  ASTERISK-30217 #close
 
-	* test_time.c: Tolerate DST transitions
+	  Change-Id: I064558e66f04b9f3260c46181812a01349761357
 
-	  When test_timezone_watch runs very near a DST transition,
-	  two time zones that would otherwise be expected to report the same
-	  time can differ because of the DST transition.
+2022-10-23 10:16 +0000 [ca8900b0f6]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Instead of having the test fail when this happens, report the
-	  times, time zones, and dst flags.
+	* tests: Fix compilation errors on 32-bit.
 
-	  ASTERISK-29722
+	  Fix compilation errors caused by using size_t
+	  instead of uintmax_t and non-portable format
+	  specifiers.
 
-	  Change-Id: Id59bdac8b277e14343ccdf0c99b89e92f79f316a
+	  ASTERISK-30273 #close
 
-2021-12-14 11:39 +0000 [0cf4e325aa]  George Joseph <gjoseph@digium.com>
+	  Change-Id: I363e6057ef84d54b88af80d23ad6147eef9216ee
 
-	* bundled_pjproject:  Add more support for multipart bodies
+2022-08-26 03:59 +0000 [12445040d3]  Henning Westerholt <hw@gilawa.com>
 
-	  Adding upstream patch for pull request...
-	  https://github.com/pjsip/pjproject/pull/2920
-	  ---------------------------------------------------------------
+	* res_pjsip: return all codecs on a re-INVITE without SDP
 
-	  sip_inv:  Additional multipart support (#2919)
+	  Currently chan_pjsip on receiving a re-INVITE without SDP will only
+	  return the codecs that are previously negotiated and not offering
+	  all enabled codecs.
 
-	  sip_inv.c:inv_check_sdp_in_incoming_msg() deals with multipart
-	  message bodies in rdata correctly. In the case where early media is
-	  involved though, the existing sdp has to be retrieved from the last
-	  tdata sent in this transaction. This, however, always assumes that
-	  the sdp sent is in a non-multipart body. While there's a function
-	  to retrieve the sdp from multipart and non-multpart rdata bodies,
-	  no similar function for tdata exists.  So...
+	  This causes interoperability issues with different equipment (e.g.
+	  from Cisco) for some of our customers and probably also in other
+	  scenarios involving 3PCC infrastructure.
 
-	  * The existing pjsip_rdata_get_sdp_info2 was refactored to
-	    find the sdp in any body, multipart or non-multipart, and
-	    from either an rdata or tdata.  The new function is
-	    pjsip_get_sdp_info.  This new function detects whether the
-	    pjsip_msg->body->data is the text representation of the sdp
-	    from an rdata or an existing pjmedia_sdp_session object
-	    from a tdata, or whether pjsip_msg->body is a multipart
-	    body containing either of the two sdp formats.
+	  According to RFC 3261, section 14.2 we SHOULD return all codecs
+	  on a re-INVITE without SDP
 
-	  * The exsting pjsip_rdata_get_sdp_info and pjsip_rdata_get_sdp_info2
-	    functions are now wrappers that get the body and Content-Type
-	    header from the rdata and call pjsip_get_sdp_info.
+	  The PR proposes a new parameter to configure this behaviour:
+	  all_codecs_on_empty_reinvite. It includes the code, documentation,
+	  alembic migrations, CHANGES file and example configuration additions.
 
-	  * Two new wrappers named pjsip_tdata_get_sdp_info and
-	    pjsip_tdata_get_sdp_info2 have been created that get the body
-	    from the tdata and call pjsip_get_sdp_info.
+	  ASTERISK-30193 #close
 
-	  * inv_offer_answer_test.c was updated to test multipart scenarios.
+	  Change-Id: I69763708d5039d512f391e296ee8a4d43a1e2148
 
-	  ASTERISK-29804
+2022-10-14 17:24 +0000 [40b52322e5]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I483c7c3d413280c9e247a96ad581278347f9c71b
+	* res_pjsip_notify: Add option support for AMI.
 
-2021-12-09 02:55 +0000 [965f4abd9a]  Frederic Van Espen <frederic.ve@gmail.com>
+	  The PJSIP notify CLI commands allow for using
+	  "options" configured in pjsip_notify.conf.
 
-	* ast_coredumper: Fix deleting results when output dir is set
+	  This allows these same options to be used in
+	  AMI actions as well.
 
-	  When OUTPUTDIR is set to another directory and the
-	  --delete-results-after is set, the resulting txt files are
-	  not deleted.
+	  Additionally, as part of this improvement,
+	  some repetitive common code is refactored.
 
-	  ASTERISK-29794 #close
+	  ASTERISK-30263 #close
 
-	  Change-Id: I1c0071f6809a1e3f5cfc455d6eb08378bc0d7286
+	  Change-Id: Ie4496b322b63b61eaf9672183a959ab99a04b6b5
 
-2021-12-13 16:49 +0000 [bb27d5e1fe]  Naveen Albert <asterisk@phreaknet.org>
+2022-07-20 19:03 +0000 [c32b39d123]  Naveen Albert <asterisk@phreaknet.org>
 
-	* pbx_variables: initialize uninitialized variable
+	* res_pjsip_logger: Add method-based logging option.
 
-	  The variable cp4 in a variable substitution function
-	  can potentially be used without being initialized
-	  currently. This causes Asterisk to no longer compile.
+	  Expands the pjsip logger to support the ability to filter
+	  by SIP message method. This can make certain types of SIP debugging
+	  easier by only logging messages of particular method(s).
 
-	  This initializes cp4 to NULL to make the compiler
-	  happy.
+	  ASTERISK-30146 #close
 
-	  ASTERISK-29803 #close
+	  Co-authored-by: Sean Bright <sean@seanbright.com>
+	  Change-Id: I9c8cbb6fc8686ef21190eb42e08bc9a9b147707f
 
-	  Change-Id: I392579cbb76db2795d5820c9427cf55fbcee9e72
+2022-10-06 11:51 +0000 [50a4495799]  Frederic LE FOLL <frederic.lefoll@c-s.fr>
 
-2021-12-08 05:24 +0000 [3d71bcd2f4]  Mark Petersen <bugs.digium.com@zombie.dk>
+	* Dialing API: Cancel a running async thread, may not cancel all calls
 
-	* app_queue.c: added DIALEDPEERNUMBER on outgoing channel
+	  race condition: ast_dial_join() may not cancel outgoing call, if
+	  function is called just after called party answer and before
+	  application execution (bit is_running_app not yet set).
 
-	  added that we set DIALEDPEERNUMBER on the outgoing channels
-	  so it is avalible in b(content^extension^line)
-	  this add the same behaviour as Dial
+	  This fix adds ast_softhangup() calls in addition to existing
+	  pthread_kill() when is_running_app is not set.
 
-	  ASTERISK-29795
+	  ASTERISK-30258
 
-	  Change-Id: Icbc589ea2066f0c401a892bf478f6b2fd44e62f6
+	  Change-Id: Idbdd5c15122159661aa8e996a42d5800083131e4
 
-2021-11-15 15:35 +0000 [05afa061f5]  Kevin Harwell <kharwell@sangoma.com>
+2022-10-23 17:46 +0000 [180ca32565]  Naveen Albert <asterisk@phreaknet.org>
 
-	* http.c: Add ability to create multiple HTTP servers
+	* chan_dahdi: Fix unavailable channels returning busy.
 
-	  Previously, it was only possible to have one HTTP server in Asterisk.
-	  With this patch it is now possible to have multiple HTTP servers
-	  listening on different addresses.
+	  This fixes dahdi_request to properly set the cause
+	  code to CONGESTION instead of BUSY if no channels
+	  were actually available.
 
-	  Note, this behavior has only been made available through an API call
-	  from within the TEST_FRAMEWORK. Specifically, this feature has been
-	  added in order to allow unit test to create/start and stop servers,
-	  if one has not been enabled through configuration.
+	  Currently, the cause is erroneously set to busy
+	  if the channel itself is found, regardless of its
+	  current state. However, if the channel is not available
+	  (e.g. T1 down, card not operable, etc.), then the
+	  channel itself may not be in a functional state,
+	  in which case CHANUNAVAIL is the correct cause to use.
 
-	  Change-Id: Ic5fb5f11e62c019a1c51310f4667b32a4dae52f5
+	  This adds a simple check to ensure that busy tone
+	  is only returned if a channel is encountered that
+	  has an owner, since that is the only possible way
+	  that a channel could actually be busy.
 
-2021-12-12 18:08 +0000 [030f7d4131]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-30274 #close
 
-	* app.c: Throw warnings for nonexistent options
+	  Change-Id: Iad5870223c081240c925b19df8d6af136953b994
 
-	  Currently, Asterisk doesn't throw warnings if options
-	  are passed into applications that don't accept them.
-	  This can confuse users if they're unaware that they
-	  are doing something wrong.
+2022-10-16 15:35 +0000 [9258d8212a]  Naveen Albert <asterisk@phreaknet.org>
 
-	  This adds an additional check to parse_options so that
-	  a warning is thrown anytime an option is parsed that
-	  doesn't exist in the parsing application, so that users
-	  are notified of the invalid usage.
+	* res_pjsip_pubsub: Prevent removing subscriptions.
 
-	  ASTERISK-29801 #close
+	  pjproject does not provide any mechanism of removing
+	  event packages, which means that once a subscription
+	  handler is registered, it is effectively permanent.
 
-	  Change-Id: Id029274a57135caca193c913307a63fd75e24679
+	  pjproject will assert if the same event package is
+	  ever registered again, so currently unloading and
+	  loading any Asterisk modules that use subscriptions
+	  will cause a crash that is beyond our control.
 
-2021-12-08 12:07 +0000 [a4c42e70c1]  Mark Petersen <bugs.digium.com@zombie.dk>
+	  For that reason, we now prevent users from being
+	  able to unload these modules, to prevent them
+	  from ever being loaded twice.
 
-	* app_voicemail.c: Support for Danish syntax in VM
+	  ASTERISK-30264 #close
 
-	  added support for playing the correct plural sound file
-	  dependen on where you have 1 or multipe messages
-	  based on the existing SE/NO code
+	  Change-Id: I7fdcb1a5e44d38b7ba10c44259fe98f0ae9bc12c
 
-	  ASTERISK-29797
+2022-09-28 07:38 +0000 [407216a0a5]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I88aa814d02f3772bb80b474204b1ffb26fe438c2
+	* say: Don't prepend ampersand erroneously.
 
-2021-12-11 20:11 +0000 [86bc3eef99]  Naveen Albert <asterisk@phreaknet.org>
+	  Some logic in say.c for determining if we need
+	  to also add an ampersand for file seperation was faulty,
+	  as non-successful files would increment the count, causing
+	  a leading ampersand to be added improperly.
 
-	* strings: Fix enum names in comment examples
+	  This is fixed, and a unit test that captures this regression
+	  is also added.
 
-	  The enum values for ast_strsep_flags includes
-	  AST_STRSEP_STRIP. However, some comments reference
-	  AST_SEP_STRIP, which doesn't exist. This fixes
-	  these comments to use the correct value.
+	  ASTERISK-30248 #close
 
-	  ASTERISK-29800 #close
+	  Change-Id: I02c1d3a11d82fe4ea8b462070cbd1effb5834d2b
 
-	  Change-Id: If7bbd0c0e6226a211d25ddf9d1629347e2674943
+2022-09-16 13:45 +0000 [d0bea5a725]  Philip Prindeville <philipp@redfish-solutions.com>
 
-2021-11-17 15:16 +0000 [c6410dc4ed]  Naveen Albert <asterisk@phreaknet.org>
+	* res_crypto: handle unsafe private key files
 
-	* configs: Updates to sample configs
+	  ASTERISK-30213 #close
 
-	  Includes some minor updates to extensions.conf
-	  and iax.conf. In particular, the demonstration
-	  of macros in extensions.conf is removed, as
-	  Macro is deprecated and will be removed soon.
-	  These examples have been replaced with examples
-	  demonstrating the usage of Gosub instead.
+	  Change-Id: I4a77143d41615b7c4fc25bb1251c0a9cb87b417a
 
-	  The older exten => ...,n syntax is also mostly
-	  replaced with the same keyword to demonstrate the
-	  newer, more concise way of defining extensions.
+2022-09-29 15:55 +0000 [907d7e7d7d]  Mike Bradeen <mbradeen@sangoma.com>
 
-	  IAXTEL no longer exists, so this example is replaced
-	  with something more generic.
+	* audiohook: add directional awareness
 
-	  Some documentation is also added to extensions.conf
-	  and iax.conf to clarify some of the new expanded
-	  encryption capabilities with IAX2.
+	  Add enum to allow setting optional direction. If set to only one
+	  direction, only feed matching-direction frames to the associated
+	  slin factory.
 
-	  ASTERISK-29758 #close
+	  This prevents mangling the transcoder on non-mixed frames when the
+	  READ and WRITE frames would have otherwise required it.  Also
+	  removes the need to mute or discard the un-wanted frames as they
+	  are no longer added in the first place.
 
-	  Change-Id: I04fba9671aa1ee9ba1bd5027061f80bbe38e7b46
+	  res_stasis_snoop is changed to use this addition to set direction
+	  on audiohook based on spy direction.
 
-2021-11-17 15:39 +0000 [2f1eb56116]  Naveen Albert <asterisk@phreaknet.org>
+	  If no direction is set, the ast_audiohook_init will init this enum
+	  to BOTH which maintains existing functionality.
 
-	* app_sendtext: Add ReceiveText application
+	  ASTERISK-30252
 
-	  Adds a ReceiveText application that can be used in
-	  conjunction with SendText. Currently, there is no
-	  way in Asterisk to receive text in the dialplan
-	  (or anywhere else, really). This allows for Asterisk
-	  to be the recipient of text instead of just the sender.
+	  Change-Id: If8716bad334562a5d812be4eeb2a92e4f3be28eb
 
-	  ASTERISK-29759 #close
+2022-06-01 11:06 +0000 [b331caca30]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Ica2c354a42bff69f323a0493d3a7cd0fb129d52d
+	* cdr: Allow bridging and dial state changes to be ignored.
 
-2021-11-20 14:37 +0000 [c6309af560]  Naveen Albert <asterisk@phreaknet.org>
+	  Allows bridging, parking, and dial messages to be globally
+	  ignored for all CDRs such that only a single CDR record
+	  is generated per channel.
 
-	* pbx_variables: Increase parsing capabilities of MSet
+	  This is useful when CDRs should endure for the lifetime of
+	  an entire channel and bridging and dial updates in the
+	  dialplan should not result in multiple CDR records being
+	  created for the call. With the ignore bridging option,
+	  bridging changes have no impact on the channel's CDRs.
+	  With the ignore dial state option, multiple Dials and their
+	  outcomes have no impact on the channel's CDRs. The
+	  last disposition on the channel is preserved in the CDR,
+	  so the actual disposition of the call remains available.
 
-	  Currently MSet can only parse a maximum of 24 variables.
-	  If more variables are provided to MSet, the 24th variable
-	  will simply contain the remainder of the string and the
-	  remaining variables thereafter will never get set.
+	  These two options can reduce the amount of "CDR hacks" that
+	  have hitherto been necessary to ensure that CDR was not
+	  "spoiled" by these messages if that was undesired, such as
+	  putting a dummy optimization-disabled local channel between
+	  the caller and the actual call and putting the CDR on the channel
+	  in the middle to ensure that CDR would persist for the entire
+	  call and properly record start, answer, and end times.
+	  Enabling these options is desirable when calls correspond
+	  to the entire lifetime of channels and the CDR should
+	  reflect that.
 
-	  This increases the number of variables that can be parsed
-	  in one go from 24 to 99. Additionally, documentation is added
-	  since this limitation is currently undocumented and is
-	  confusing to users who encounter this limitation.
+	  Current default behavior remains unchanged.
 
-	  ASTERISK-29766 #close
+	  ASTERISK-30091 #close
 
-	  Change-Id: I3fe35b462dedec0a452fd9ea7f92c920a3939f16
+	  Change-Id: I393981af42732ec5ac3ff9266444abb453b7c832
 
-2021-11-23 20:21 +0000 [3108457d8f]  Naveen Albert <asterisk@phreaknet.org>
+2022-09-30 06:24 +0000 [e0e7f35730]  Naveen Albert <asterisk@phreaknet.org>
 
-	* chan_sip: Fix crash when accessing RURI before initiating outgoing call
+	* res_tonedetect: Add ringback support to TONE_DETECT.
 
-	  Attempting to access ${CHANNEL(ruri)} in a pre-dial handler before
-	  initiating an outgoing call will cause Asterisk to crash. This is
-	  because a null field is accessed, resulting in an offset from null and
-	  subsequent memory access violation.
+	  Adds support for detecting audible ringback tone
+	  to the TONE_DETECT function using the p option.
 
-	  Since RURI is not guaranteed to exist, we now check if the base
-	  pointer is non-null before calculating an offset.
+	  ASTERISK-30254 #close
 
-	  ASTERISK-29772
+	  Change-Id: Ie2329ff245248768367d26749c285fbe823f6414
 
-	  Change-Id: Icd3b02f07256bbe6615854af5717074087b95a83
+2022-10-01 17:08 +0000 [98fc05f13b]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-10-25 16:19 +0000 [c0cdaf0246]  Naveen Albert <asterisk@phreaknet.org>
+	* chan_dahdi: Resolve format truncation warning.
 
-	* func_json: Adds JSON_DECODE function
+	  Fixes a format truncation warning in notify_message.
 
-	  Adds the JSON_DECODE function for parsing JSON in the
-	  dialplan. JSON parsing already exists in the Asterisk
-	  core and is used for many different things. This
-	  function exposes the basic parsing capability to
-	  the user in the dialplan, for instance, in conjunction
-	  with CURL for using API responses.
+	  ASTERISK-30256 #close
 
-	  ASTERISK-29706 #close
+	  Change-Id: I983a423c0214641ca4f8c9dfe0b19c47448fdee1
 
-	  Change-Id: Iea60c49a7358dfdc2db60803cdc9a742f808ba2c
+2022-09-16 18:29 +0000 [ef74ecacc7]  Philip Prindeville <philipp@redfish-solutions.com>
 
-2021-12-11 18:45 +0000 [087f25d3fd]  Sean Bright <sean.bright@gmail.com>
+	* res_crypto: don't modify fname in try_load_key()
 
-	* CHANGES: Correct reference to configuration file.
+	  "fname" is passed in as a const char *, but strstr() mangles that
+	  into a char *, and we were attempting to modify the string in place.
+	  This is an unwanted (and undocumented) side-effect.
 
-	  Change-Id: I22a788ebf11168fff7fbf9ea956ebcd705ab63dd
+	  ASTERISK-30213
 
-2021-11-15 15:08 +0000 [cc1418ef47]  Naveen Albert <asterisk@phreaknet.org>
+	  Change-Id: Ifa36d352aafeb7f9beec3f746332865c7d21e629
 
-	* pbx: Add variable substitution API for extensions
+2022-09-15 22:45 +0000 [5e2485b5c0]  Philip Prindeville <philipp@redfish-solutions.com>
 
-	  Currently, variable substitution involving dialplan
-	  extensions is quite clunky since it entails obtaining
-	  the current dialplan location, backing it up, storing
-	  the desired variables for substitution on the channel,
-	  performing substitution, then restoring the original
-	  location.
+	* res_crypto: use ast_file_read_dirs() to iterate
 
-	  In addition to being clunky, things could also go wrong
-	  if an async goto were to occur and change the dialplan
-	  location during a substitution.
+	  ASTERISK-30213
 
-	  Fundamentally, there's no reason it needs to be done this
-	  way, so new API is added to allow for directly passing in
-	  the dialplan location for the purposes of variable
-	  substitution so we don't need to mess with the channel
-	  information anymore. Existing API is not changed.
+	  Change-Id: I115f5f8942ffcfb23cd2559a55bac8a2eba081e0
 
-	  ASTERISK-29745 #close
+2022-09-27 09:35 +0000 [2a500b325a]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: I23273bf27fa0efb64a606eebf9aa8e2f41a065e4
+	* res_geolocation: Update wiki documentation
 
-2021-09-21 19:18 +0000 [2b2b708d43]  Naveen Albert <asterisk@phreaknet.org>
+	  Also added a note to the geolocation.conf.sample file
+	  and added a README to the res/res_geolocation/wiki
+	  directory.
 
-	* app_mf: Add full tech-agnostic MF support
+	  Change-Id: I89c3c5db8c0701b33127993622d5e4f904bddfbc
 
-	  Adds tech-agnostic support for MF signaling by adding
-	  MF sender and receiver applications as well as Dial
-	  integration.
+2022-07-26 07:01 +0000 [0d2e140123]  Maximilian Fridrich <m.fridrich@commend.com>
 
-	  ASTERISK-29496-mf #do-not-close
+	* res_pjsip: Add mediasec capabilities.
 
-	  Change-Id: I61962b359b8ec4cfd05df877ddf9f5b8f71927a4
+	  This patch adds support for mediasec SIP headers and SDP attributes.
+	  These are defined in RFC 3329, 3GPP TS 24.229 and
+	  draft-dawes-sipcore-mediasec-parameter. The new features are
+	  implemented so that a backbone for RFC 3329 is present to streamline
+	  future work on RFC 3329.
 
-2021-12-06 04:25 +0000 [55dd77b921]  Alexander Traud <pabstraud@compuserve.com>
+	  With this patch, Asterisk can communicate with Deutsche Telekom trunks
+	  which require these fields.
 
-	* xmldoc: Avoid whitespace around value for parameter/required.
+	  ASTERISK-30032
 
-	  Otherwise, the value 'false' was not found in the enumerated set of
-	  the XML DTD for the XML attribute 'required' in the XML element
-	  'parameter'. Therefore, DTD validation of the runtime XML failed.
+	  Change-Id: Ia7f5b5ba42db18074fdd5428c4e1838728586be2
 
-	  ASTERISK-29790
+2022-09-28 07:44 +0000 [7f80830ced]  Asterisk Development Team <asteriskteam@digium.com>
 
-	  Change-Id: Id13f230ad65a70dd8c2e3ae9ac85d1e841aed03e
+	* Update CHANGES and UPGRADE.txt for 20.0.0
+2022-09-19 19:53 +0000 [62881c668b]  Holger Hans Peter Freyther <holger@moiji-mobile.com>
 
-2021-12-04 02:36 +0000 [4b6c72572c]  Alexander Traud <pabstraud@compuserve.com>
+	* res_prometheus: Do not crash on invisible bridges
 
-	* progdocs: Fix Doxygen left-overs.
+	  Avoid crashing by skipping invisible bridges and checking the
+	  snapshot for a null pointer. In effect this is how the bridges
+	  are enumerated in res/ari/resource_bridges.c already.
 
-	  Change-Id: I5b5cf9c9cbbe00ba8b379a8d162ac67445d39016
+	  ASTERISK-30239
+	  ASTERISK-30237
 
-2021-12-06 05:17 +0000 [5b24edeb7c]  Alexander Traud <pabstraud@compuserve.com>
+	  Change-Id: I58ef9f44036feded5966b5fc70ae754f8182883d
 
-	* xmldoc: Correct definition for XML element 'matchInfo'.
+2022-09-19 12:35 +0000 [8afb313a43]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29791
+	* res_pjsip_geolocation: Change some notices to debugs.
 
-	  Change-Id: I7c656498427fcadd0a5d61a54ff67e6036609725
+	  If geolocation is not in use for an endpoint, the NOTICE
+	  log level is currently spammed with messages about this,
+	  even though nothing is wrong and these messages provide
+	  no real value. These log messages are therefore changed
+	  to debugs.
 
-2021-11-23 08:05 +0000 [d914e14420]  Alexander Traud <pabstraud@compuserve.com>
+	  ASTERISK-30241 #close
 
-	* progdocs: Update Makefile.
+	  Change-Id: I656b355d812f67cc0f0fdf09b00b0e1458598bb4
 
-	  In developer mode, use internal documentation as well.
-	  This should produce no warnings. Fix yours!
+2022-09-24 07:08 +0000 [7335b0cffe]  Birger Harzenetter (license 5870)
 
-	  In noisy mode, output all possible warnings of Doxygen.
-	  This creates zillion of warnings. Double-check your current module!
+	* db: Fix incorrect DB tree count for AMI.
 
-	  Any warnings are in the file './doxygen.log'. Beside that, this change
-	  avoids deprecated parameters because the configuration file for Doxygen
-	  contains only those parameters which differ from the default. This
-	  avoids the need to update the file on each run. Furthermore, it adds
-	  AST_VECTOR to be expanded. Finally, the default name for that file is
-	  Doxyfile. Therefore, let us use that!
+	  The DBGetTree AMI action's ListItem previously
+	  always reported 1, regardless of the count. This
+	  is corrected to report the actual count.
 
-	  ASTERISK-26991
-	  ASTERISK-20259
+	  ASTERISK-30245 #close
+	  patches:
+	    gettreecount.diff submitted by Birger Harzenetter (license 5870)
 
-	  Change-Id: I4129092a199d5e24c319a09cd088614b121015af
+	  Change-Id: I46d8992710f1b8524426b1255f57d1ef4a4934d4
 
-2021-12-03 07:38 +0000 [a103956fc9]  Alexander Traud <pabstraud@compuserve.com>
+2022-09-21 18:17 +0000 [407167cc28]  Naveen Albert <asterisk@phreaknet.org>
 
-	* res_pjsip_sdp_rtp: Do not warn on unknown sRTP crypto suites.
+	* func_logic: Don't emit warning if both IF branches are empty.
 
-	  res_sdp_crypto_parse_offer(.) emits many log messages already.
+	  The IF function currently emits warnings if both IF branches
+	  are empty. However, there is no actual necessity that either
+	  branch be non-empty as, unlike other conditional applications/
+	  functions, nothing is inherently done with IF, and both
+	  sides could legitimately be empty. The warning is thus turned
+	  into a debug message.
 
-	  ASTERISK-29785
+	  ASTERISK-30243 #close
 
-	  Change-Id: I1a191ebe4fec1102946d4e31887e5197ca02dfe8
+	  Change-Id: I5250625dd720f95e1859b5dfb933905d7e7a730e
 
-2021-11-30 14:16 +0000 [c37cc5d3bc]  Sean Bright <sean.bright@gmail.com>
+2022-09-11 17:13 +0000 [a5ec60e6c6]  Naveen Albert <asterisk@phreaknet.org>
 
-	* channel: Short-circuit ast_channel_get_by_name() on empty arg.
+	* features: Add no answer option to Bridge.
 
-	  We know that passing a NULL or empty argument to
-	  ast_channel_get_by_name() will never result in a matching channel and
-	  will always result in an error being emitted, so just short-circuit
-	  out in that case.
+	  Adds the n "no answer" option to the Bridge application
+	  so that answer supervision can not automatically
+	  be provided when Bridge is executed.
 
-	  ASTERISK-28219 #close
+	  Additionally, a mechanism (dialplan variable)
+	  is added to prevent bridge targets (typically the
+	  target of a masquerade) from answering the channel
+	  when they enter the bridge.
 
-	  Change-Id: I88eadc748e9c6996fc17467b0a05881bbfd00bce
+	  ASTERISK-30223 #close
 
-2021-10-26 16:12 +0000 [04d00c203c]  Mike Bradeen <mbradeen@sangoma.com>
+	  Change-Id: I76f73fcd8e403bcd18f2abb40c658f537ac1ba6d
 
-	* res_rtp_asterisk: Addressing possible rtp range issues
+2022-09-08 19:12 +0000 [1e29607b5c]  Naveen Albert <asterisk@phreaknet.org>
 
-	  res/res_rtp_asterisk.c: Adding 1 to rtpstart if it is deteremined
-	  that rtpstart was configured to be an odd value. Also adding a loop
-	  counter to prevent a possible infinite loop when looking for a free
-	  port.
+	* app_bridgewait: Add option to not answer channel.
 
-	  ASTERISK-27406
+	  Adds the n option to not answer the channel when calling
+	  BridgeWait, so the application can be used without
+	  forcing answer supervision.
 
-	  Change-Id: I90f07deef0716da4a30206e9f849458b2dbe346b
+	  ASTERISK-30216 #close
 
-2021-08-24 09:56 +0000 [6c9e8afd4e]  Mark Petersen <bugs.digium.com@zombie.dk>
+	  Change-Id: I6b85ef300b1f7b5170f8537e2b10889cc2e6605a
 
-	* apps/app_dial.c: HANGUPCAUSE reason code for CANCEL is set to AST_CAUSE_NORMAL_CLEARING
+2022-08-15 14:59 +0000 [8c791f9a65]  Naveen Albert <asterisk@phreaknet.org>
 
-	  changed that when we recive a CANCEL that we set HANGUPCAUSE to AST_CAUSE_NORMAL_CLEARING
+	* app_amd: Add option to play audio during AMD.
 
-	  ASTERISK-28053
-	  Reported by: roadkill
+	  Adds an option that will play an audio file
+	  to the party while AMD is running on the
+	  channel, so the called party does not just
+	  hear silence.
 
-	  Change-Id: Ib653aec2282f55b59d87484391cc07c8e6612b89
+	  ASTERISK-30179 #close
 
-2021-11-19 02:54 +0000 [178cb0ffe4]  Alexander Traud <pabstraud@compuserve.com>
+	  Change-Id: I4af306274552b61b3d9f0883c33f698abd4699b6
 
-	* res: Fix for Doxygen.
+2022-09-15 22:35 +0000 [3e7ce90f9c]  Philip Prindeville <philipp@redfish-solutions.com>
 
-	  These are the remaining issues found in /res.
+	* test: initialize capture structure before freeing
 
-	  ASTERISK-29761
+	  ASTERISK-30232 #close
 
-	  Change-Id: I572e6019c422780dde5ce8448b6c85c77af6046d
+	  Change-Id: I2603e2cef8f93f6b0a6ef39f7eac744251bb3902
 
-2021-11-08 18:30 +0000 [b2e71b82e7]  Dustin Marquess <jailbird@fdf.net>
+2021-05-17 09:19 +0000 [1ed4518328]  Naveen Albert <mail@interlinked.x10host.com>
 
-	* res_fax_spandsp: Add spandsp 3.0.0+ compatibility
+	* func_export: Add EXPORT function
 
-	  Newer versions of spandsp did refactoring of code to add new features
-	  like color FAXing. This refactoring broke backwards compatibility.
-	  Add support for the new version while retaining support for 0.0.6.
+	  Adds the EXPORT function, which allows write
+	  access to variables and functions on other
+	  channels.
 
-	  ASTERISK-29729 #close
+	  ASTERISK-29432 #close
 
-	  Change-Id: I3bd74550604ebcf0304528d647fa39abc62fbaa1
+	  Change-Id: I7492645ae4307553d0f586d78e13a4f586231fdf
 
-2021-11-19 09:47 +0000 [20d9158c9c]  Alexander Traud <pabstraud@compuserve.com>
+2022-07-26 08:40 +0000 [5bbad0d27c]  Maximilian Fridrich <m.fridrich@commend.com>
 
-	* main: Fix for Doxygen.
+	* res_pjsip: Add 100rel option "peer_supported".
 
-	  ASTERISK-29763
+	  This patch adds a new option to the 100rel parameter for pjsip
+	  endpoints called "peer_supported". When an endpoint with this option
+	  receives an incoming request and the request indicated support for the
+	  100rel extension, then Asterisk will send 1xx responses reliably. If
+	  the request did not indicate 100rel support, Asterisk sends 1xx
+	  responses normally.
 
-	  Change-Id: Ib8359e3590a9109eb04a5376559d040e5e21867e
+	  ASTERISK-30158
 
-2021-12-02 18:26 +0000  Asterisk Development Team <asteriskteam@digium.com>
+	  Change-Id: Id6d95ffa8f00dab118e0b386146e99f254f287ad
 
-	* asterisk 18.9.0-rc1 Released.
+2022-09-10 10:15 +0000 [8aae0b9f08]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-12-02 11:59 +0000 [868d2d5e53]  Asterisk Development Team <asteriskteam@digium.com>
+	* func_scramble: Fix null pointer dereference.
 
-	* Update CHANGES and UPGRADE.txt for 18.9.0
-2021-11-28 04:29 +0000 [f946b92553]  Alexander Traud <pabstraud@compuserve.com>
+	  Fix segfault due to null pointer dereference
+	  inside the audiohook callback.
 
-	* progdocs: Fix for Doxygen, the hidden parts.
+	  ASTERISK-30220 #close
 
-	  ASTERISK-29779
+	  Change-Id: Ideb80f606974366e89d619d908744230b5a5a259
 
-	  Change-Id: If338163488498f65fa7248b60e80299c0a928e4b
+2022-09-05 01:59 +0000 [278c5726ca]  Jaco Kroon <jaco@uls.co.za>
 
-2021-11-12 10:05 +0000 [751bbf4b97]  Alexander Traud <pabstraud@compuserve.com>
+	* manager: be more aggressive about purging http sessions.
 
-	* progdocs: Fix grouping for latest Doxygen.
+	  If we find that n_max (currently hard wired to 1) sessions were purged,
+	  schedule the next purge for 1ms into the future rather than 5000ms (as
+	  per current).  This way we will purge up to 1000 sessions per second
+	  rather than 1 every 5 seconds.
 
-	  Since Doxygen 1.8.16, a special comment block is required. Otherwise
-	  (pure C comment), the group command is ignored. Additionally, several
-	  unbalanced group commands were fixed.
+	  This mitigates a build-up of sessions should http sessions gets
+	  established faster than 1 per 5 seconds.
 
-	  ASTERISK-29732
+	  Change-Id: I9820d39aa080109df44fe98c1325cafae48d54f5
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
 
-	  Change-Id: I4687857b9d56e6f44fd440b73af156691660202e
+2022-09-11 15:40 +0000 [ab1dbfef75]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-11-25 12:41 +0000 [bcb7aee723]  Naveen Albert <asterisk@phreaknet.org>
+	* func_strings: Add trim functions.
 
-	* documentation: Standardize examples
+	  Adds TRIM, LTRIM, and RTRIM, which can be used
+	  for trimming leading and trailing whitespace
+	  from strings.
 
-	  Most examples in the XML documentation use the
-	  example tag to demonstrate examples, which gets
-	  parsed specially in the Wiki to make it easier
-	  to follow for users.
+	  ASTERISK-30222 #close
 
-	  This fixes a few modules to use the example
-	  tag instead of vanilla para tags to bring them
-	  in line with the standard syntax.
+	  Change-Id: I50fb0c40726d044a7a41939fa9026f3da4872554
 
-	  ASTERISK-29777 #close
+2022-09-16 09:58 +0000 [e25b690d10]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: I9acb6cc5faf1d220e73c6dd28592371d768d279b
+	* res_crypto: Memory issues and uninitialized variable errors
 
-2021-11-28 14:52 +0000 [04ac4fe509]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK-30235
 
-	* config.c: Prevent UB in ast_realtime_require_field.
+	  Change-Id: Ia1e326e7b52cd06fd5e6c9009e3e63193c92f6cd
 
-	  A backend's implementation of the realtime 'require' function may call
-	  va_arg() and then fail, leaving the va_list in an undefined
-	  state. Pass a copy of the va_list instead.
+2022-09-16 08:43 +0000 [e33f2dcc0f]  George Joseph <gjoseph@digium.com>
 
-	  ASTERISK-29771 #close
+	* res_geolocation: Fix issues exposed by compiling with -O2
 
-	  Change-Id: I555565a72af84e96d49f62fe8cb66ba5a78461f4
+	  Fixed "may be used uninitialized" errors in geoloc_config.c.
 
-2021-11-01 10:40 +0000 [70cdb0f9a8]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-30234
 
-	* app_voicemail: Refactor email generation functions
+	  Change-Id: I1ea336bf7abbc16fa59b75720f0db8f1d960b3d4
 
-	  Refactors generic functions used for email generation
-	  into utils.c so that they can be used by multiple
-	  modules, including app_voicemail and app_minivm,
-	  to avoid code duplication.
+2022-09-13 14:41 +0000 [026dc08529]  Philip Prindeville <philipp@redfish-solutions.com>
 
-	  ASTERISK-29715 #close
+	* res_crypto: don't complain about directories
 
-	  Change-Id: I1de0ed3483623e9599711129edc817c45ad237ee
+	  ASTERISK-30226 #close
 
-2021-11-25 10:34 +0000 [b290bb1251]  Alexander Traud <pabstraud@compuserve.com>
+	  Change-Id: I5695fb0c9521f112f754b8362cff2a8f3eff05c5
 
-	* stir/shaken: Avoid a compiler extension of GCC.
+2022-09-14 14:50 +0000  Asterisk Development Team <asteriskteam@digium.com>
 
-	  ASTERISK-29776
+	* asterisk 20.0.0-rc1 Released.
 
-	  Change-Id: I86e5eca66fb775a5744af0c929fb269e70575a73
+2022-09-14 09:25 +0000 [f01ed3eea4]  Asterisk Development Team <asteriskteam@digium.com>
 
-2021-11-20 04:00 +0000 [858c9e1d80]  Alexander Traud <pabstraud@compuserve.com>
+	* Update CHANGES and UPGRADE.txt for 20.0.0
+2022-08-15 14:30 +0000 [7a44296ca9]  Mike Bradeen <mbradeen@sangoma.com>
 
-	* chan_misdn: Fix for Doxygen.
+	* res_pjsip: Add user=phone on From and PAID for usereqphone=yes
 
-	  ASTERISK-29764
+	  Adding user=phone to local-side uri's when user_eq_phone=yes is set for
+	  an endpoint. Previously this would only add the header to the To and R-URI.
 
-	  Change-Id: I6e5466cce03e25695c5c7d8b68c305184dcf5375
+	  ASTERISK-30178
 
-2021-11-23 07:45 +0000 [422f5389f6]  Alexander Traud <pabstraud@compuserve.com>
+	  Change-Id: Id3bfb5d225d762e7d2668c023fe09e4541ae8600
 
-	* progdocs: Remove outdated references in doxyref.h.
+2022-09-13 08:14 +0000 [8cbea1c7ef]  George Joseph <gjoseph@digium.com>
 
-	  ASTERISK-29773
+	* res_geolocation: Fix segfault when there's an empty element
 
-	  Change-Id: Ica93160d9158cc0e80c5fda829b80d1b49a6b9b9
+	  Fixed a segfault caused by var_list_from_loc_info() encountering
+	  an empty location info element.
 
-2021-11-20 06:05 +0000 [31e385bebb]  Alexander Traud <pabstraud@compuserve.com>
+	  Fixed an issue in ast_strsep() where a value with only whitespace
+	  wasn't being preserved.
 
-	* xmldoc: Fix for Doxygen.
+	  Fixed an issue in ast_variable_list_from_quoted_string() where
+	  an empty value was considered a failure.
 
-	  ASTERISK-29765
+	  ASTERISK-30215
+	  Reported by: Dan Cropp
 
-	  Change-Id: I654ba0debe8351038d4433716434a09370f04c9d
+	  Change-Id: Ieca64e061a6d9298f0196c694b60d986ef82613a
 
-2021-10-28 02:28 +0000 [89237be105]  Jaco Kroon <jaco@uls.co.za>
+2022-08-13 11:32 +0000 [80bc844fd6]  sungtae kim <pchero21@gmail.com>
 
-	* logger: use __FUNCTION__ instead of __PRETTY_FUNCTION__
+	* res_musiconhold: Add option to not play music on hold on unanswered channels
 
-	  This avoids a few long-name overflows, at the cost of less instructive
-	  names in the case of C++ (specifically overloaded functions and class
-	  methods).  This in turn is offset against the fact that we're logging
-	  the filename and line numbers in any case.
+	  This change adds an option, answeredonly, that will prevent music on
+	  hold on channels that are not answered.
 
-	  Change-Id: I54101a0bb5f8cb9ef63ec12c5e0d4c8edafff9ed
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+	  ASTERISK-30135
 
-2021-11-16 16:34 +0000 [ea941032ff]  Mike Bradeen <mbradeen@sangoma.com>
+	  Change-Id: I1ab0defa43a29a26ae39f94c623596cf90fddc08
 
-	* astobj2.c: Fix core when ref_log enabled
+2022-08-02 12:15 +0000 [881a3f2306]  Ben Ford <bford@digium.com>
 
-	  In the AO2_ALLOC_OPT_LOCK_NOLOCK case the referenced obj
-	  structure is freed, but is then referenced later if ref_log is
-	  enabled. The change is to store the obj->priv_data.options value
-	  locally and reference it instead of the value from the freed obj
+	* res_pjsip: Add TEL URI support for basic calls.
 
-	  ASTERISK-29730
+	  This change allows TEL URI requests to come through for basic calls. The
+	  allowed requests are INVITE, ACK, BYE, and CANCEL. The From and To
+	  headers will now allow TEL URIs, as well as the request URI.
 
-	  Change-Id: I60cc5dc1f5a4330e7ad56976fc38a42de0ab6072
+	  Support is only for TEL URIs present in traffic from a remote party.
+	  Asterisk does not generate any TEL URIs on its own.
 
-2021-11-19 03:46 +0000 [3f86c95cf5]  Alexander Traud <pabstraud@compuserve.com>
+	  ASTERISK-26894
 
-	* channels: Fix for Doxygen.
+	  Change-Id: If5729e6cd583be7acf666373bf9f1b9d653ec29a
 
-	  ASTERISK-29762
+2022-03-24 14:22 +0000 [3e054c9ebc]  Philip Prindeville <philipp@redfish-solutions.com>
 
-	  Change-Id: Ia8811ac12b93ff8c18164699c6fbc604cb0a23f7
+	* res_crypto: Use EVP API's instead of legacy API's
 
-2021-11-16 04:06 +0000 [7d4e37a180]  Joshua C. Colp <jcolp@sangoma.com>
+	  ASTERISK-30046 #close
 
-	* bridge: Deny full Local channel pair in bridge.
+	  Change-Id: I5c738756de75fd27ebad54be144c0ac6193f21b2
 
-	  Local channels are made up of two pairs - the 1 and 2
-	  sides. When a frame goes in one side, it comes out the
-	  other. Back and forth. When both halves are in a
-	  bridge this creates an infinite loop of frames.
+2022-05-03 19:27 +0000 [736cdf84f4]  Philip Prindeville <philipp@redfish-solutions.com>
 
-	  This change makes it so that bridging no longer
-	  allows both of these sides to exist in the same
-	  bridge.
+	* test: Add coverage for res_crypto
 
-	  ASTERISK-29748
+	  We're validating the following functionality:
 
-	  Change-Id: I29928b6de87cd9be996a77daccefd7c360fef651
+	  encrypting a block of data with RSA
+	  decrypting a block of data with RSA
+	  signing a block of data with RSA
+	  verifying a signature with RSA
+	  encrypting a block of data with AES-ECB
+	  encrypting a block of data with AES-ECB
 
-2021-11-06 18:35 +0000 [ca2e13e18f]  Naveen Albert <asterisk@phreaknet.org>
+	  as well as accessing test keys from the keystore.
 
-	* res_tonedetect: Add call progress tone detection
+	  ASTERISK-30045 #close
 
-	  Makes basic call progress tone detection available
-	  in a tech-agnostic manner with the addition of the
-	  ToneScan application. This can determine if the channel
-	  has encountered a busy signal, SIT tones, dial tone,
-	  modem, fax machine, etc. A few basic async progress
-	  tone detect options are also added to the TONE_DETECT
-	  function.
+	  Change-Id: I0d10e7b41009c5290a4356c6480e636712d5c96d
 
-	  ASTERISK-29720 #close
+2022-07-26 12:38 +0000 [2d7656cb50]  Philip Prindeville <philipp@redfish-solutions.com>
 
-	  Change-Id: Ia02437e0450473031e294798b8cb421fb8f24e90
+	* res_crypto: make keys reloadable on demand for testing
 
-2021-11-08 13:59 +0000 [70b14f3eda]  Boris P. Korzun <drtr0jan@yandex.ru>
+	  ASTERISK-30045
 
-	* rtp_engine: Add type field for JSON RTCP Report stasis messages
+	  Change-Id: If59bbb50c1771084bfe2fef307a6077c90d35ce8
 
-	  ASTERISK-29727 #close
+2022-05-03 13:12 +0000 [5809d879b0]  Philip Prindeville <philipp@redfish-solutions.com>
 
-	  Change-Id: I2eca8aeb591cb63ac2238d08eab662367453cb82
+	* test: Add test coverage for capture child process output
 
-2021-11-17 03:24 +0000 [783b775946]  Alexander Traud <pabstraud@compuserve.com>
+	  ASTERISK-30037 #close
 
-	* odbc: Fix for Doxygen.
+	  Change-Id: I0273e85eeeb6b8e46703f24cd74d84f3daf0a69a
 
-	  ASTERISK-29754
+2022-07-26 14:38 +0000 [2c4c44ca64]  Philip Prindeville <philipp@redfish-solutions.com>
 
-	  Change-Id: Ia09eb68d283d201d9a6fbeccfc0efe83fe0502a5
+	* main/utils: allow checking for command in $PATH
 
-2021-11-17 02:54 +0000 [c549eda0a7]  Alexander Traud <pabstraud@compuserve.com>
+	  ASTERISK-30037
 
-	* parking: Fix for Doxygen.
+	  Change-Id: I4b6f7264c8c737c476c798d2352f3232b263bbdf
 
-	  ASTERISK-29753
+2022-05-02 23:49 +0000 [b9df2c481b]  Philip Prindeville <philipp@redfish-solutions.com>
 
-	  Change-Id: I7a61974584f6169502e6860fc711919fe7bbfaa7
+	* test: Add ability to capture child process output
 
-2021-11-17 05:43 +0000 [b4eebfa191]  Alexander Traud <pabstraud@compuserve.com>
+	  ASTERISK-30037
 
-	* ari-stubs: Avoid 'is' as comparism with an literal.
+	  Change-Id: Icbf84ce05addb197a458361c35d784e460d8d6c2
 
-	  Python 3.9.7 gave a syntax warning.
+2022-04-26 20:44 +0000 [d13afaf302]  Philip Prindeville <philipp@redfish-solutions.com>
 
-	  Change-Id: I3e3a982fe720726bc0015bcdb0e638a626ec89d4
+	* res_crypto: Don't load non-regular files in keys directory
 
-2021-11-17 04:26 +0000 [b5962fe528]  Alexander Traud <pabstraud@compuserve.com>
+	  ASTERISK-30046
 
-	* frame: Fix for Doxygen.
+	  Change-Id: Ie77e0648f8b0b1c2159fb24662d1989cfd4cc36d
 
-	  ASTERISK-29755
+2022-09-08 09:12 +0000 [2dac2bf8dc]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I8240013ec3db0669c0acf67e26bf6c9cbb5b72af
+	* func_frame_trace: Remove bogus assertion.
 
-2021-11-17 06:18 +0000 [5b5a9ea4f0]  Alexander Traud <pabstraud@compuserve.com>
+	  The FRAME_TRACE function currently asserts if it sees
+	  a MASQUERADE_NOTIFY. However, this is a legitimate thing
+	  that can happen so asserting is inappropriate, as there
+	  are no clear negative ramifications of such a thing. This
+	  is adjusted to be like the other frames to print out
+	  the subclass.
 
-	* res_ari: Fix for Doxygen.
+	  ASTERISK-30210 #close
 
-	  ASTERISK-29756
+	  Change-Id: I8ecbdcf17e35f64bdeab42868471f581ad1d1a56
 
-	  Change-Id: I2f1c1eea1c902492b77b74de9950f20ebbb7e758
+2022-07-27 14:54 +0000 [c487425620]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-11-16 12:07 +0000 [e27b91d542]  Alexander Traud <pabstraud@compuserve.com>
+	* lock.c: Add AMI event for deadlocks.
 
-	* channel: Fix for Doxygen.
+	  Adds an AMI event to indicate that a deadlock
+	  has likely started, when Asterisk is compiled
+	  with DETECT_DEADLOCKS enabled. This can make
+	  it easier to perform automated deadlock detection
+	  and take appropriate action (such as doing a core
+	  dump). Unlike the deadlock warnings, the AMI event
+	  is emitted only once per deadlock.
 
-	  ASTERISK-29751
+	  ASTERISK-30161 #close
 
-	  Change-Id: Ie04da5029c57ebee44733bdf05013156abe80176
+	  Change-Id: Ifc6ed3e390f8b4cff7f8077a50e4d7a5b54e42fb
 
-2021-11-08 10:08 +0000 [53610679bf]  Alexander Traud <pabstraud@compuserve.com>
+2022-09-04 14:38 +0000 [205c7c8d21]  Naveen Albert <asterisk@phreaknet.org>
 
-	* BuildSystem: Consistently allow 'ye' even for Jansson.
+	* app_confbridge: Add end_marked_any option.
 
-	  Furthermore, consistently use not 'No' but ':' for non-existent file
-	  paths. Finally, use the same pattern for checking file paths:
-	    a)  = ":"
-	    b) != "x:"
+	  Adds the end_marked_any option, which can be used
+	  to kick a user from a conference if any marked user
+	  leaves.
 
-	  Change-Id: I0c80c76d2cc98b0e5c859131290f4e3141a1a544
+	  ASTERISK-30211 #close
 
-2021-11-16 10:26 +0000 [6988386234]  Alexander Traud <pabstraud@compuserve.com>
+	  Change-Id: I9e8da7ccb892e522546c0f2b5476d172e022c2f5
 
-	* stasis: Fix for Doxygen.
+2022-09-03 18:19 +0000 [2de016b181]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29750
+	* pbx_variables: Use const char if possible.
 
-	  Change-Id: Iea50173e785b2e9d49bc24c0af7111cfd96d44a9
+	  Use const char for char arguments to
+	  pbx_substitute_variables_helper_full_location
+	  that can do so (context and exten).
 
-2021-11-17 02:30 +0000 [e7d5db1471]  Alexander Traud <pabstraud@compuserve.com>
+	  ASTERISK-30209 #close
 
-	* app: Fix for Doxygen.
+	  Change-Id: I001357177e9c3dca2b2b4eebc5650c1095b3da6f
 
-	  ASTERISK-29752
+2022-08-25 08:00 +0000 [05f42806cc]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: If40cbd01d47a6cfd620b18206dedb8460216c8af
+	* res_geolocation: Add two new options to GEOLOC_PROFILE
 
-2021-11-16 06:51 +0000 [31c26fcbc6]  Alexander Traud <pabstraud@compuserve.com>
+	  Added an 'a' option to the GEOLOC_PROFILE function to allow
+	  variable lists like location_info_refinement to be appended
+	  to instead of replacing the entire list.
 
-	* res_xmpp: Fix for Doxygen.
+	  Added an 'r' option to the GEOLOC_PROFILE function to resolve all
+	  variables before a read operation and after a Set operation.
 
-	  ASTERISK-29749
+	  Added a few missing parameters to the ones allowed for writing
+	  with GEOLOC_PROFILE.
 
-	  Change-Id: I7885793b63bdeaa883e76edb899bbba9660eb1c5
+	  Fixed a bug where calling GEOLOC_PROFILE to read a parameter
+	  might actually update the profile object.
 
-2021-11-15 07:38 +0000 [026c6d51b1]  Alexander Traud <pabstraud@compuserve.com>
+	  Cleaned up XML documentation a bit.
 
-	* addons: Fix for Doxygen.
+	  ASTERISK-30190
 
-	  ASTERISK-29742
+	  Change-Id: I75f541db43345509a2e86225bfa4cf8e242e5b6c
 
-	  Change-Id: Ie752cb9638ced1ebe3a55d710c6c18ef6bd0aafc
+2022-08-18 07:29 +0000 [c799db6a21]  George Joseph <gjoseph@digium.com>
 
-2021-11-16 03:55 +0000 [bae495601a]  Alexander Traud <pabstraud@compuserve.com>
+	* res_geolocation:  Allow location parameters on the profile object
 
-	* res_pjsip: Fix for Doxygen.
+	  You can now specify the location object's format, location_info,
+	  method, location_source and confidence parameters directly on
+	  a profile object for simple scenarios where the location
+	  information isn't common with any other profiles.  This is
+	  mutually exclusive with setting location_reference on the
+	  profile.
 
-	  ASTERISK-29747
+	  Updated appdocsxml.dtd to allow xi:include in a configObject
+	  element.  This makes it easier to link to complete configOptions
+	  in another object.  This is used to add the above fields to the
+	  profile object without having to maintain the option descriptions
+	  in two places.
 
-	  Change-Id: Ic7a1e9453f805a6264fe86c96b7d18b87b376084
+	  ASTERISK-30185
 
-2021-11-13 06:04 +0000 [cb043633d4]  Alexander Traud <pabstraud@compuserve.com>
+	  Change-Id: Ifd5f05be0a76f0a6ad49fa28d17c394027677569
 
-	* chan_iax2: Fix for Doxygen.
+2022-08-17 08:15 +0000 [4ffc5561c4]  George Joseph <gjoseph@digium.com>
 
-	  ASTERISK-29737
+	* res_geolocation: Add profile parameter suppress_empty_ca_elements
 
-	  Change-Id: I282003cc553989fd5c19ceeac9e478fa4ee06cec
+	  Added profile parameter "suppress_empty_ca_elements" that
+	  will cause Civic Address elements that are empty to be
+	  suppressed from the outgoing PIDF-LO document.
 
-2021-11-15 08:12 +0000 [42055f4a65]  Alexander Traud <pabstraud@compuserve.com>
+	  Fixed a possible SEGV if a sub-parameter value didn't have a
+	  value.
 
-	* bridges: Fix for Doxygen.
+	  ASTERISK-30177
 
-	  ASTERISK-29743
+	  Change-Id: I924ccc5aa2f45110a3155b22e53dfaf3ef2092dd
 
-	  Change-Id: I6e1bbbaa5875e19994a328ab40a5d429c6010e8b
+2022-08-16 07:25 +0000 [2d5a6498dd]  George Joseph <gjoseph@digium.com>
 
-2021-11-15 07:29 +0000 [1a9df88d98]  Alexander Traud <pabstraud@compuserve.com>
+	* res_geolocation:  Add built-in profiles
 
-	* tests: Fix for Doxygen.
+	  The trigger to perform outgoing geolocation processing is the
+	  presence of a geoloc_outgoing_call_profile on an endpoint. This
+	  is intentional so as to not leak location information to
+	  destinations that shouldn't receive it.   In a totally dynamic
+	  configuration scenario however, there may not be any profiles
+	  defined in geolocation.conf.  This makes it impossible to do
+	  outgoing processing without defining a "dummy" profile in the
+	  config file.
 
-	  ASTERISK-29741
+	  This commit adds 4 built-in profiles:
+	    "<prefer_config>"
+	    "<discard_config>"
+	    "<prefer_incoming>"
+	    "<discard_incoming>"
+	  The profiles are empty except for having their precedence
+	  set and can be set on an endpoint to allow processing without
+	  entries in geolocation.conf.  "<discard_config>" is actually the
+	  best one to use in this situation.
 
-	  Change-Id: I012d72b237bda2ef2d0f86307dfc6dc7add4b54b
+	  ASTERISK-30182
 
-2021-11-15 07:18 +0000 [09bac49a01]  Alexander Traud <pabstraud@compuserve.com>
+	  Change-Id: I1819ccfa404ce59802a3a07ad1cabed60fb9480a
 
-	* apps: Fix for Doxygen.
+2022-08-30 08:01 +0000 [f3de933b16]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  ASTERISK-29740
+	* res_pjsip_sdp_rtp: Skip formats without SDP details.
 
-	  Change-Id: Icb6fbcfea0a5f1c82caa5001902b6a786adbf307
+	  When producing an outgoing SDP we iterate through the configured
+	  formats and produce SDP information. It is possible for some
+	  configured formats to not have SDP information available. If this
+	  is the case we skip over them to allow the SDP to still be
+	  produced.
 
-2021-11-12 12:41 +0000 [44a9c16e9c]  Alexander Traud <pabstraud@compuserve.com>
+	  ASTERISK-29185
 
-	* progdocs: Avoid 'name' with Doxygen \file.
+	  Change-Id: I3e37569aa4ca341260e6ca5904dc2f75e46a1749
 
-	  Fixes four misuses of the parameter 'name'. Additionally, for
-	  consistency and to avoid such an issue in future, those few other
-	  places, which used '\file name', were changed just to '\file'. Then,
-	  Doxygen uses the name of the current file.
+2022-05-03 07:53 +0000 [c7612521be]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29733
+	* cli: Prevent assertions on startup from bad ao2 refs.
 
-	  Change-Id: I0c18b4c863c6988b138c77448057349a9ee7052d
+	  If "core show channels" is run before startup has completed, it
+	  is possible for bad ao2 refs to occur because the system is not
+	  yet fully initialized. This will lead to an assertion failing.
 
-2021-11-13 04:40 +0000 [d08792ceba]  Alexander Traud <pabstraud@compuserve.com>
+	  To prevent this, initialization of CLI builtins is moved to be
+	  later along in the main load sequence. Core CLI commands are
+	  loaded at the same time, but channel-related commands are loaded
+	  later on.
 
-	* bridge_channel: Fix for Doxygen.
+	  ASTERISK-29846 #close
 
-	  ASTERISK-29736
+	  Change-Id: If6b3cde802876bd738c1b4cf2683bea6ddc615b6
 
-	  Change-Id: Ia5370289e6526001a6b52754b533bcea1a9d7e5c
+2022-08-19 08:24 +0000 [a0713a9f70]  Joshua C. Colp <jcolp@sangoma.com>
 
-2021-11-12 13:52 +0000 [57b4956a8a]  Alexander Traud <pabstraud@compuserve.com>
+	* pjsip: Add TLS transport reload support for certificate and key.
 
-	* progdocs: Avoid multiple use of section labels.
+	  This change adds support using the pjsip_tls_transport_restart
+	  function for reloading the TLS certificate and key, if the filenames
+	  remain unchanged. This is useful for Let's Encrypt and other
+	  situations. Note that no restart of the transport will occur if
+	  the certificate and key remain unchanged.
 
-	  ASTERISK-29735
+	  ASTERISK-30186
 
-	  Change-Id: I56935e73f7bd1d4ae2721d11040f4835da64b810
+	  Change-Id: I9bc95a6bf791830a9491ad9fa43c17d4010028d0
 
-2021-11-12 13:17 +0000 [23b16c5372]  Alexander Traud <pabstraud@compuserve.com>
+2022-08-25 06:51 +0000 [754346a4a9]  Naveen Albert <asterisk@phreaknet.org>
 
-	* progdocs: Use Doxygen \example correctly.
+	* res_tonedetect: Fix typos referring to wrong variables.
 
-	  ASTERISK-29734
+	  Fixes two typos that cause fax detection to not work.
+	  One refers to the wrong frame variable, and the other
+	  refers to the subclass.integer instead of the frametype
+	  as it should.
 
-	  Change-Id: I83b51e85cd71867645ab3a8a820f8fd1f065abd2
+	  ASTERISK-30192 #close
 
-2021-10-30 20:04 +0000 [4bc3dc6543]  Josh Soref <jsoref@users.noreply.github.com>
+	  Change-Id: I7b35fdb7bcf25a29a212eee37c20812c64ab3ef1
 
-	* bridges: Spelling fixes
+2022-08-17 13:30 +0000 [46776c77c4]  Mike Bradeen <mbradeen@sangoma.com>
 
-	  Correct typos of the following word families:
+	* alembic: add missing ps_endpoints columns
 
-	  multiplication
-	  potentially
-	  iteration
-	  interaction
-	  virtual
-	  synthesis
-	  convolve
-	  initializes
-	  overlap
+	  The following required columns were missing,
+	  now added to the ps_endpoints table:
 
-	  ASTERISK-29714
+	  incoming_call_offer_pref
+	  outgoing_call_offer_pref
+	  stir_shaken_profile
 
-	  Change-Id: Ia40f1aca8f2996ab407c6ed9d24cb10a67c6684b
-	  (cherry picked from commit 2a8fb4695ef87c363fc49f142b696ceed85a7f98)
+	  ASTERISK-29453
 
-2021-11-15 13:02 +0000 [721026ff37]  Naveen Albert <asterisk@phreaknet.org>
+	  Change-Id: I5cf565edf30195844d6acbc1e1de8c5f0d837568
 
-	* app_morsecode: Fix deadlock
+2022-08-19 11:02 +0000 [583e017f34]  Sean Bright <sean@seanbright.com>
 
-	  Fixes a deadlock in app_morsecode caused by locking
-	  the channel twice when reading variables from the
-	  channel. The duplicate lock is simply removed.
+	* chan_dahdi.c: Resolve a format-truncation build warning.
 
-	  ASTERISK-29744 #close
+	  With gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0:
 
-	  Change-Id: I204000701f123361d7f85e0498fedc90243c75e4
+	  > chan_dahdi.c:4129:18: error: ‘%s’ directive output may be truncated
+	  >   writing up to 255 bytes into a region of size between 242 and 252
+	  >   [-Werror=format-truncation=]
 
-2021-10-24 13:38 +0000 [1cd2584b27]  Naveen Albert <asterisk@phreaknet.org>
+	  This removes the error-prone sizeof(...) calculations in favor of just
+	  doubling the size of the base buffer.
 
-	* res_pjsip_callerid: Fix OLI parsing
+	  Change-Id: I2d276785286730d3d5d0a921bcea2e065dbf27c5
 
-	  Fix parsing of ANI2/OLI information, since it was previously
-	  parsing the user, when it should have been parsing other_param.
+2022-08-03 09:55 +0000 [12c4c1bf5f]  Alexei Gradinari <alex2grad@gmail.com>
 
-	  Also improves the parsing by using pjproject native functions
-	  rather than trying to parse the parameters ourselves like
-	  chan_sip did. A previous attempt at this caused a crash, but
-	  this works correctly now.
+	* res_pjsip_pubsub: Postpone destruction of old subscriptions on RLS update
 
-	  ASTERISK-29703 #close
+	  Set termination state to old subscriptions to prevent queueing and sending
+	  NOTIFY messages on exten/device state changes.
 
-	  Change-Id: I8f3c79032d9ea1a21d16f8e11f22bd8d887738a1
+	  Postpone destruction of old subscriptions until all already queued tasks
+	  that may be using old subscriptions have completed.
 
-2021-10-25 12:51 +0000 [3c4b7cef64]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-29906
 
-	* app_read: Fix custom terminator functionality regression
+	  Change-Id: I96582aad3a26515ca73a8460ee6756f56f6ba23b
 
-	  Currently, when the t option is specified with no arguments,
-	  the # character is still treated as a terminator, even though
-	  no character should be treated as a terminator.
+2022-08-15 07:34 +0000 [155c796203]  Sean Bright <sean@seanbright.com>
 
-	  This is because a previous regression fix was modified to
-	  remove the use of NULL as a default altogether. However,
-	  NULL and an empty string actually refer to different
-	  arrangements and should be treated differently. NULL is the
-	  default terminator (#), while an empty string removes the
-	  terminator altogether. This is the behavior being used by
-	  the rest of the core.
+	* channel.h: Remove redundant declaration.
 
-	  Additionally, since S_OR catches empty strings as well as
-	  NULL (not intended), this is changed to a ternary operator
-	  instead, which fixes the behavior.
+	  The DECLARE_STRINGFIELD_SETTERS_FOR() declares ast_channel_name_set()
+	  for us, so no need to declare it separately.
 
-	  ASTERISK-29705 #close
+	  Change-Id: I4813a884ada475ddc62bca480bceb4a53b3ec59a
 
-	  Change-Id: I9b6b72196dd04f5b1e0ab5aa1b0adf627725e086
+2022-02-05 06:13 +0000 [3fa66c92b5]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-10-30 20:04 +0000 [59715a073b]  Josh Soref <jsoref@users.noreply.github.com>
+	* features: Add transfer initiation options.
 
-	* utils: Spelling fixes
+	  Adds additional control options over the transfer
+	  feature functionality to give users more control
+	  in how the transfer feature sounds and works.
 
-	  Correct typos of the following word families:
+	  First, the "transfer" sound that plays when a transfer is
+	  initiated can now be customized by the user in
+	  features.conf, just as with the other transfer sounds.
 
-	  command-line
-	  immediately
-	  extensions
-	  momentarily
-	  mustn't
-	  numbered
-	  bytes
-	  caching
+	  Secondly, the user can now specify the transfer extension
+	  in advance by using the TRANSFER_EXTEN variable. If
+	  a valid extension is contained in this variable, the call
+	  will automatically be transferred to this destination.
+	  Otherwise, it will fall back to collecting the extension
+	  from the user as is always done now.
 
-	  ASTERISK-29714
+	  ASTERISK-29899 #close
 
-	  Change-Id: I8b2b125c5d4d2f9e87a58515c97468ad47ca44f8
+	  Change-Id: Ibff309caa459a2b958706f2ed0ca393b1ef502e3
 
-2021-10-30 20:04 +0000 [3685e55673]  Josh Soref <jsoref@users.noreply.github.com>
+2022-08-31 14:16 +0000 [adffb975dc]  Mike Bradeen <mbradeen@sangoma.com>
 
-	* pbx: Spelling fixes
+	* CI: Fixing path issue on venv check
 
-	  Correct typos of the following word families:
+	  ASTERISK-26826
 
-	  process
-	  populate
-	  with
-	  africa
-	  accessing
-	  contexts
-	  exercise
-	  university
-	  organizations
-	  withhold
-	  maintaining
-	  independent
-	  rotation
-	  ignore
-	  eventname
+	  Change-Id: I07388d16f74452cebc9c981f99044eb6b77df792
 
-	  ASTERISK-29714
+2022-08-11 13:39 +0000 [4fc9e06db1]  Mike Bradeen <mbradeen@sangoma.com>
 
-	  Change-Id: I90eacc5bc3dcf75a9c898cfb85164f37dec08345
+	* CI: use Python3 virtual environment
 
-2021-10-30 20:04 +0000 [4cf87f6175]  Josh Soref <jsoref@users.noreply.github.com>
+	  Requires Python3 testsuite changes
 
-	* rest-api-templates: Spelling fixes
+	  ASTERISK-26826
 
-	  Correct typos of the following word families:
+	  Change-Id: I92ec7dec751ad455503a584d6e860db88c56d6bc
 
-	  overwritten
-	  descendants
+2022-07-28 16:12 +0000 [e2e049e473]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29714
+	* general: Very minor coding guideline fixes.
 
-	  Change-Id: I2307e35887a3437e50317a4b86f0893f25f9fd3b
+	  Fixes a few coding guideline violations:
+	  * Use of C99 comments
+	  * Opening brace on same line as function prototype
 
-2021-10-30 20:04 +0000 [c1b21bee6d]  Josh Soref <jsoref@users.noreply.github.com>
+	  ASTERISK-30163 #close
 
-	* channels: Spelling fixes
+	  Change-Id: I07771c4c89facd41ce8d323859f022ddbddf6ca7
 
-	  Correct typos of the following word families:
+2022-08-05 08:50 +0000 [8a8416e365]  George Joseph <gjoseph@digium.com>
 
-	  appease
-	  permanently
-	  overriding
-	  residue
-	  silliness
-	  extension
-	  channels
-	  globally
-	  reference
-	  japanese
-	  group
-	  coordinate
-	  registry
-	  information
-	  inconvenience
-	  attempts
-	  cadence
-	  payloads
-	  presence
-	  provisioning
-	  mimics
-	  behavior
-	  width
-	  natively
-	  syslabel
-	  not owning
-	  unquelch
-	  mostly
-	  constants
-	  interesting
-	  active
-	  unequipped
-	  brodmann
-	  commanding
-	  backlogged
-	  without
-	  bitstream
-	  firmware
-	  maintain
-	  exclusive
-	  practically
-	  structs
-	  appearance
-	  range
-	  retransmission
-	  indication
-	  provisional
-	  associating
-	  always
-	  whether
-	  cyrillic
-	  distinctive
-	  components
-	  reinitialized
-	  initialized
-	  capability
-	  switches
-	  occurring
-	  happened
-	  outbound
+	* res_geolocation: Address user issues, remove complexity, plug leaks
 
-	  ASTERISK-29714
+	  * Added processing for the 'confidence' element.
+	  * Added documentation to some APIs.
+	  * removed a lot of complex code related to the very-off-nominal
+	    case of needing to process multiple location info sources.
+	  * Create a new 'ast_geoloc_eprofile_to_pidf' API that just takes
+	    one eprofile instead of a datastore of multiples.
+	  * Plugged a huge leak in XML processing that arose from
+	    insufficient documentation by the libxml/libxslt authors.
+	  * Refactored stylesheets to be more efficient.
+	  * Renamed 'profile_action' to 'profile_precedence' to better
+	    reflect it's purpose.
+	  * Added the config option for 'allow_routing_use' which
+	    sets the value of the 'Geolocation-Routing' header.
+	  * Removed the GeolocProfileCreate and GeolocProfileDelete
+	    dialplan apps.
+	  * Changed the GEOLOC_PROFILE dialplan function as follows:
+	    * Removed the 'profile' argument.
+	    * Automatically create a profile if it doesn't exist.
+	    * Delete a profile if 'inheritable' is set to no.
+	  * Fixed various bugs and leaks
+	  * Updated Asterisk WiKi documentation.
 
-	  Change-Id: Ife52ee89cd2170b684fa651ca72b1cb911a57339
+	  ASTERISK-30167
 
-2021-10-30 20:04 +0000 [4fc59ccc92]  Josh Soref <jsoref@users.noreply.github.com>
+	  Change-Id: If38c23f26228e96165be161c2f5e849cb8e16fa0
 
-	* tests: Spelling fixes
+2022-07-30 16:15 +0000 [ff044c222b]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Correct typos of the following word families:
+	* chan_iax2: Add missing options documentation.
 
-	  mounting
-	  jitterbuffer
-	  thrashing
-	  original
-	  manipulating
-	  entries
-	  actual
-	  possibility
-	  tasks
-	  options
-	  positives
-	  taskprocessor
-	  other
-	  dynamic
-	  declarative
+	  Adds missing dial resource option documentation.
 
-	  ASTERISK-29714
+	  ASTERISK-30164 #close
 
-	  Change-Id: I6b94659d045eec5d8d020fce2e9b6e2f593dfeb6
+	  Change-Id: I674e1fc9b1e5d67a20599bd4b418ce294d48fc83
 
-2021-10-30 20:04 +0000 [c3978efef6]  Josh Soref <jsoref@users.noreply.github.com>
+2022-07-31 19:30 +0000 [dc7ec11c26]  Naveen Albert <asterisk@phreaknet.org>
 
-	* Makefile: Spelling fixes
+	* app_confbridge: Fix memory leak on updated menu options.
 
-	  Correct typos of the following word families:
+	  If the CONFBRIDGE function is used to dynamically set
+	  menu options, a memory leak occurs when a menu option
+	  that has been set is overridden, since the menu entry
+	  is not destroyed before being freed. This ensures that
+	  it is.
 
-	  libraries
-	  install
-	  overwrite
+	  Additionally, logic that duplicates the destroy function
+	  is removed in lieu of the destroy function itself.
 
-	  ASTERISK-29714
+	  ASTERISK-28422 #close
 
-	  Change-Id: I6488814f79186d6c23dfd7b7f9bba0a046126174
+	  Change-Id: I71cfb5c24e636984d41086d1333a416dc12ff995
 
-2021-10-30 20:04 +0000 [2a8b651b7e]  Josh Soref <jsoref@users.noreply.github.com>
+2022-07-19 09:05 +0000 [30d7a212b0]  George Joseph <gjoseph@digium.com>
 
-	* contrib: Spelling fixes
+	* Geolocation: Wiki Documentation
 
-	  Correct typos of the following word families:
+	  Change-Id: I68ba22db0a69d9e2eabcc2141b48a2395f7f1a23
 
-	  standard
-	  increase
-	  comments
-	  valgrind
-	  promiscuous
-	  editing
-	  libtonezone
-	  storage
-	  aggressive
-	  whitespace
-	  russellbryant
-	  consecutive
-	  peternixon
+2022-07-28 06:10 +0000 [f4a020a45b]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29714
+	* manager: Remove documentation for nonexistent action.
 
-	  Change-Id: I9cafbf41b579c9c0c84c81719d2c4f900beec245
+	  The manager XML documentation documents a "FilterList"
+	  action, but there is no such action. Therefore, this can
+	  lead to confusion when people try to use a documented
+	  action that does not, in fact, exist. This is removed
+	  as the action never did exist in the past, nor would it
+	  be trivial to add since we only store the regex_t
+	  objects, so the filter list can't actually be provided
+	  without storing that separately. Most likely, the
+	  documentation was originally added (around version 10)
+	  in anticipation of something that never happened.
 
-2021-10-30 20:04 +0000 [3ac7afe09c]  Josh Soref <jsoref@users.noreply.github.com>
+	  ASTERISK-29917 #close
 
-	* formats: Spelling fixes
+	  Change-Id: I846b16fd6f80a91d4ddc5d8a861b522d7c6f8f97
 
-	  Correct typos of the following word families:
+2022-07-22 15:57 +0000 [c654486547]  Naveen Albert <asterisk@phreaknet.org>
 
-	  truncate
+	* general: Improve logging levels of some log messages.
 
-	  ASTERISK-29714
+	  Adjusts some logging levels to be more or less important,
+	  that is more prominent when actual problems occur and less
+	  prominent for less noteworthy things.
 
-	  Change-Id: I6507760c72b919873cff7cac22b3781036cd4955
+	  ASTERISK-30153 #close
 
-2021-10-30 20:04 +0000 [49ef881eb4]  Josh Soref <jsoref@users.noreply.github.com>
+	  Change-Id: Ifc8f7df427aa018627db462125ae744986d3261b
 
-	* addons: Spelling fixes
+2022-07-27 13:34 +0000 [5feebc0857]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Correct typos of the following word families:
+	* cdr.conf: Remove obsolete app_mysql reference.
 
-	  definition
-	  listener
-	  fastcopy
-	  logical
-	  registration
-	  classify
-	  documentation
-	  explicitly
-	  dialed
-	  endpoint
-	  elements
-	  arithmetic
-	  might
-	  prepend
-	  byte
-	  terminal
-	  inquiry
-	  skipping
-	  aliases
-	  calling
-	  absent
-	  authentication
-	  transmit
-	  their
-	  ericsson
-	  disconnecting
-	  redir
-	  items
-	  client
-	  adapter
-	  transmitter
-	  existing
-	  satisfies
-	  pointer
-	  interval
-	  supplied
+	  The CDR sample config still mentions that app_mysql
+	  is available in the addons directory, but this is
+	  incorrect as it was removed as of 19. This removes
+	  that to avoid confusion.
 
-	  ASTERISK-29714
+	  ASTERISK-30160 #close
 
-	  Change-Id: I8548438246f7b718d88e0b9e0a1eb384bbec88e4
+	  Change-Id: Ie5293ccb4f2b365896981811b480544e67bb9cd7
 
-2021-10-30 20:04 +0000 [70af726dcd]  Josh Soref <jsoref@users.noreply.github.com>
+2022-07-27 13:28 +0000 [165368bf0b]  Naveen Albert <asterisk@phreaknet.org>
 
-	* agi: Spelling fixes
+	* general: Remove obsolete SVN references.
 
-	  Correct typos of the following word families:
+	  There are a handful of files in the tree that
+	  reference an SVN link for the coding guidelines.
 
-	  pretend
-	  speech
+	  This removes these because the links are dead
+	  and the vast majority of source files do not
+	  contain these links, so this is more consistent.
 
-	  ASTERISK-29714
+	  app_skel still maintains an (up to date) link
+	  to the coding guidelines.
 
-	  Change-Id: I7d0527c329cda07552247ea11b2d7db207a3d87d
+	  ASTERISK-30159 #close
 
-2021-10-30 20:04 +0000 [c0fafa1863]  Josh Soref <jsoref@users.noreply.github.com>
+	  Change-Id: I35bbb20f66982e98099cff3029ede20091ffdac7
 
-	* funcs: Spelling fixes
+2022-07-23 18:14 +0000 [2d8f2696b2]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Correct typos of the following word families:
+	* app_confbridge: Add missing AMI documentation.
 
-	  effectively
-	  emitted
-	  expect
-	  anthony
+	  Documents the ConfbridgeListRooms AMI response,
+	  which is currently not documented.
 
-	  ASTERISK-29714
+	  ASTERISK-30020 #close
 
-	  Change-Id: Ic16f9ec855bb6d14ec8e170b90af9a36b06d488a
+	  Change-Id: Id6fff7a936244bae7b52686301eb740c1169cdea
 
-2021-10-30 20:04 +0000 [8fb9588e8c]  Josh Soref <jsoref@users.noreply.github.com>
+2022-07-23 18:07 +0000 [4af881506e]  Naveen Albert <asterisk@phreaknet.org>
 
-	* build_tools: Spelling fixes
+	* app_meetme: Add missing AMI documentation.
 
-	  Correct typos of the following word families:
+	  The MeetmeList and MeetmeListRooms AMI
+	  responses are currently completely undocumented.
+	  This adds documentation for these responses.
 
-	  binutils
+	  ASTERISK-30018 #close
 
-	  ASTERISK-29714
+	  Change-Id: Id93135b7edf01de6f8fba266e2122989dc8996b8
 
-	  Change-Id: I2f676ab48cd50edc400c43307cb53679e4c09b97
+2022-07-23 17:37 +0000 [83912496ab]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-10-30 20:04 +0000 [7a59a9365a]  Josh Soref <jsoref@users.noreply.github.com>
+	* func_srv: Document field parameter.
 
-	* menuselect: Spelling fixes
+	  Adds missing documentation for the field parameter
+	  for the SRVRESULT function.
 
-	  Correct typos of the following word families:
+	  ASTERISK-30151
+	  Reported by: Chris Young
 
-	  dependency
-	  unless
-	  random
-	  dependencies
-	  delimited
-	  randomly
-	  modules
+	  Change-Id: I4385a2e0892a07e30dea1a8a0588e2c1bea2b1f1
 
-	  ASTERISK-29714
+2022-07-23 17:17 +0000 [c771e2dd7a]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I3920603a8dc7c0a1852d2f885e06b1144692d40e
+	* pbx_functions.c: Manually update ast_str strlen.
 
-2021-10-30 20:04 +0000 [b13acf3ae6]  Josh Soref <jsoref@users.noreply.github.com>
+	  When ast_func_read2 is used to read a function using
+	  its read function (as opposed to a native ast_str read2
+	  function), the result is copied directly by the function
+	  into the ast_str buffer. As a result, the ast_str length
+	  remains initialized to 0, which is a bug because this is
+	  not the real string length.
 
-	* include: Spelling fixes
+	  This can cascade and have issues elsewhere, such as when
+	  reading substrings of functions that only register read
+	  as opposed to read2 callbacks. In this case, since reading
+	  ast_str_strlen returns 0, the returned substring is empty
+	  as opposed to the actual substring. This has caused
+	  the ast_str family of functions to behave inconsistently
+	  and erroneously, in contrast to the pbx_variables substitution
+	  functions which work correctly.
 
-	  Correct typos of the following word families:
+	  This fixes this issue by manually updating the ast_str length
+	  when the result is copied directly into the ast_str buffer.
 
-	  activities
-	  forward
-	  occurs
-	  unprepared
-	  association
-	  compress
-	  extracted
-	  doubly
-	  callback
-	  prometheus
-	  underlying
-	  keyframe
-	  continue
-	  convenience
-	  calculates
-	  ignorepattern
-	  determine
-	  subscribers
-	  subsystem
-	  synthetic
-	  applies
-	  example
-	  manager
-	  established
-	  result
-	  microseconds
-	  occurrences
-	  unsuccessful
-	  accommodates
-	  related
-	  signifying
-	  unsubscribe
-	  greater
-	  fastforward
-	  itself
-	  unregistering
-	  using
-	  translator
-	  sorcery
-	  implementation
-	  serializers
-	  asynchronous
-	  unknowingly
-	  initialization
-	  determining
-	  category
-	  these
-	  persistent
-	  propagate
-	  outputted
-	  string
-	  allocated
-	  decremented
-	  second
-	  cacheability
-	  destructor
-	  impaired
-	  decrypted
-	  relies
-	  signaling
-	  based
-	  suspended
-	  retrieved
-	  functions
-	  search
-	  auth
-	  considered
+	  Additionally, an assertion and a unit test that previously
+	  exposed these issues are added, now that the issue is fixed.
 
-	  ASTERISK-29714
+	  ASTERISK-29966 #close
 
-	  Change-Id: I542ce887a16603f886a915920d5710d4a0a1358d
+	  Change-Id: I4e2dba41410f9d4dff61c995d2ca27718248e07f
 
-2021-10-30 20:04 +0000 [135d51e55e]  Josh Soref <jsoref@users.noreply.github.com>
+2022-02-18 15:59 +0000 [f645157a4b]  Sergey V. Lobanov <sergey@lobanov.in>
 
-	* doc: Spelling fixes
+	* build: fix bininstall launchd issue on cross-platform build
 
-	  Correct typos of the following word families:
+	  configure script detects /sbin/launchd, but the result of this
+	  check is not used in Makefile (bininstall). Makefile also detects
+	  /sbin/launchd file to decide if it is required to install
+	  safe_asterisk.
 
-	  transparent
-	  roughly
+	  configure script correctly detects cross compile build and sets
+	  PBX_LAUNCHD=0
 
-	  ASTERISK-29714
+	  In case of building asterisk on MacOS host for Linux target using
+	  external toolchain (e.g. OpenWrt toolchain), bininstall does not
+	  install safe_asterisk (due to /sbin/launchd detection in Makefile),
+	  but it is required on target (Linux).
 
-	  Change-Id: I2b90c68dfde4aa3f0d58f64f8187465336acb1b3
+	  This patch adds HAVE_SBIN_LAUNCHD=@PBX_LAUNCHD@ to makeopts.in to
+	  use the result of /sbin/launchd detection from configure script in
+	  Makefile.
+	  Also this patch uses HAVE_SBIN_LAUNCHD in Makefile (bininstall) to
+	  decide if it is required to install safe_asterisk.
 
-2021-10-30 20:04 +0000 [ae83d927d8]  Josh Soref <jsoref@users.noreply.github.com>
+	  ASTERISK-29905 #close
 
-	* configs: Spelling fixes
+	  Change-Id: Iff61217276cd188f43f51ef4cdbffe39d9f07f65
 
-	  Correct typos of the following word families:
+2022-07-11 06:32 +0000 [a9223f210e]  Naveen Albert <asterisk@phreaknet.org>
 
-	  password
-	  excludes
-	  undesirable
-	  checksums
-	  through
-	  screening
-	  interpreting
-	  database
-	  causes
-	  initiation
-	  member
-	  busydetect
-	  defined
-	  severely
-	  throughput
-	  recognized
-	  counter
-	  require
-	  indefinitely
-	  accounts
+	* db: Add AMI action to retrieve DB keys at prefix.
 
-	  ASTERISK-29714
+	  Adds the DBGetTree action, which can be used to
+	  retrieve all of the DB keys beginning with a
+	  particular prefix, similar to the capability
+	  provided by the database show CLI command.
 
-	  Change-Id: Ie8f2a7b274a162dd627ee6a2165f5e8a3876527e
+	  ASTERISK-30136 #close
 
-2021-10-30 20:04 +0000 [dcf492e7b6]  Josh Soref <jsoref@users.noreply.github.com>
+	  Change-Id: I3be9425e53be71f24303fdd4d2923c14e84337e6
 
-	* res: Spelling fixes
+2022-07-12 16:38 +0000 [ce18196280]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Correct typos of the following word families:
+	* manager: Fix incomplete filtering of AMI events.
 
-	  identifying
-	  structures
-	  actcount
-	  initializer
-	  attributes
-	  statement
-	  enough
-	  locking
-	  declaration
-	  userevent
-	  provides
-	  unregister
-	  session
-	  execute
-	  searches
-	  verification
-	  suppressed
-	  prepared
-	  passwords
-	  recipients
-	  event
-	  because
-	  brief
-	  unidentified
-	  redundancy
-	  character
-	  the
-	  module
-	  reload
-	  operation
-	  backslashes
-	  accurate
-	  incorrect
-	  collision
-	  initializing
-	  instance
-	  interpreted
-	  buddies
-	  omitted
-	  manually
-	  requires
-	  queries
-	  generator
-	  scheduler
-	  configuration has
-	  owner
-	  resource
-	  performed
-	  masquerade
-	  apparently
-	  routable
+	  The global event filtering code was only in one
+	  possible execution path, so not all events were
+	  being properly filtered out if requested. This moves
+	  that into the universal AMI handling code so all
+	  events are properly handled.
 
-	  ASTERISK-29714
+	  Additionally, the CLI listing of disabled events can
+	  also get truncated, so we now print out everything.
 
-	  Change-Id: I88485116d2c59b776aa2e1f8b4ce8239a21decda
+	  ASTERISK-30137 #close
 
-2021-10-30 20:04 +0000 [ccfebc3cfc]  Josh Soref <jsoref@users.noreply.github.com>
+	  Change-Id: If8c42edcb2abc5158552da7eba2a8ff6b20e1959
 
-	* codecs: Spelling fixes
+2022-07-20 05:59 +0000 [f8000daff5]  George Joseph <gjoseph@digium.com>
 
-	  Correct typos of the following word families:
+	* Update defaultbranch to 20
 
-	  voiced
-	  denumerator
-	  codeword
-	  upsampling
-	  constructed
-	  residual
-	  subroutine
-	  conditional
-	  quantizing
-	  courtesy
-	  number
+	  Change-Id: Ib91db9223a78188667e15053bcc73931b878414e
 
-	  ASTERISK-29714
+2022-07-20 05:44 +0000 [a818b05ca1]  Asterisk Development Team <asteriskteam@digium.com>
 
-	  Change-Id: I471fb8086a5277d8f05047fedee22cfa97a4252d
+	* Update CHANGES and UPGRADE.txt for 20.0.0
+2022-06-14 04:12 +0000 [37c16f9eef]  Michael Neuhauser <mike@firmix.at>
 
-2021-10-30 20:04 +0000 [4019a93edf]  Josh Soref <jsoref@users.noreply.github.com>
+	* res_pjsip: delay contact pruning on Asterisk start
 
-	* main: Spelling fixes
+	  Move the call to ast_sip_location_prune_boot_contacts() *after* the call
+	  to ast_res_pjsip_init_options_handling() so that
+	  res/res_pjsip/pjsip_options.c is informed about the contact deletion and
+	  updates its sip_options_contact_statuses list. This allows for an AMI
+	  event to be sent by res/res_pjsip/pjsip_options.c if the endpoint
+	  registers again from the same remote address and port (i.e., same URI)
+	  as used before the Asterisk restart.
 
-	  Correct typos of the following word families:
+	  ASTERISK-30109
+	  Reported-by: Michael Neuhauser
 
-	  analysis
-	  nuisance
-	  converting
-	  although
-	  transaction
-	  desctitle
-	  acquire
-	  update
-	  evaluate
-	  thousand
-	  this
-	  dissolved
-	  management
-	  integrity
-	  reconstructed
-	  decrement
-	  further on
-	  irrelevant
-	  currently
-	  constancy
-	  anyway
-	  unconstrained
-	  featuregroups
-	  right
-	  larger
-	  evaluated
-	  encumbered
-	  languages
-	  digits
-	  authoritative
-	  framing
-	  blindxfer
-	  tolerate
-	  traverser
-	  exclamation
-	  perform
-	  permissions
-	  rearrangement
-	  performing
-	  processing
-	  declension
-	  happily
-	  duplicate
-	  compound
-	  hundred
-	  returns
-	  elicit
-	  allocate
-	  actually
-	  paths
-	  inheritance
-	  atxferdropcall
-	  earlier
-	  synchronization
-	  multiplier
-	  acknowledge
-	  across
-	  against
-	  thousands
-	  joyous
-	  manipulators
-	  guaranteed
-	  emulating
-	  soundfile
+	  Change-Id: I1ba4478019e4931a7085f62708d9b66837e901a8
 
-	  ASTERISK-29714
+2022-03-28 20:35 +0000 [f2f397c1a8]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I926ba4b11e9f6dd3fdd93170ab1f9b997910be70
+	* chan_dahdi: Fix buggy and missing Caller ID parameters
 
-2021-10-30 20:04 +0000 [998ad0e179]  Josh Soref <jsoref@users.noreply.github.com>
+	  There are several things wrong with analog Caller ID
+	  handling that are fixed by this commit:
 
-	* CREDITS: Spelling fixes
+	  callerid.c's Caller ID generation function contains the
+	  logic to use the presentation to properly send the proper
+	  Caller ID. However, currently, DAHDI does not pass any
+	  presentation information to the Caller ID module, which
+	  means that presentation is completely ignored on all calls.
+	  This means that lines could be getting Caller ID information
+	  they aren't supposed to.
 
-	  Correct typos of the following word families:
+	  Part of the reason this has been obscured is because the
+	  simple switch logic for handling the built in *67 and *82
+	  is completely wrong. Rather than modifying the presentation
+	  for the call accordingly (which is what it's supposed to do),
+	  it simply blanks out the Caller ID or fills it in. This is
+	  wrong, so wrong that it makes a mockery of the specification.
+	  Additionally, it would leave to the "UNAVAILABLE" disposition
+	  being used for Caller ID generation as opposed to the "PRIVATE"
+	  disposition that it should have been using. This is now fixed
+	  to only update the presentation and not modify the number and
+	  name, so that the simple switch *67/*82 work correctly.
 
-	  contributors
+	  Next, sig_analog currently only copies over the name and number,
+	  nothing else, when it is filling in a duplicated caller id
+	  structure. Thus, we also now copy over the presentation
+	  information so that is available for the Caller ID spill.
+	  Additionally, this meant that "valid" was implicitly 0,
+	  and as such presentation would always fail to "Unavailable".
+	  The validity is therefore also copied over so it can be used
+	  by ast_party_id_presentation.
 
-	  ASTERISK-29714
+	  As part of this fix, new API is added so that all the relevant
+	  Caller ID information can be passed in to the Caller ID generation
+	  functions. Parameters that are also completely missing from the
+	  Caller ID spill have also been added, to enhance the compatibility,
+	  correctness, and completeness of the Asterisk Caller ID implementation.
 
-	  Change-Id: I6f46dae8bf8125a21ce8ff318380b2b412d9d2f9
+	  ASTERISK-29991 #close
 
-2021-10-30 20:04 +0000 [e7b1dcf769]  Josh Soref <jsoref@users.noreply.github.com>
+	  Change-Id: Icc44a5e09979916f4c18a440f96e10dc1c76ae15
 
-	* apps: Spelling fixes
+2022-07-10 17:31 +0000 [be6a03f68c]  Sam Banks <sam.banks.nz@gmail.com>
 
-	  Correct typos of the following word families:
+	* queues.conf.sample: Correction of typo
 
-	  simultaneously
-	  administrator
-	  directforward
-	  attachfmt
-	  dailplan
-	  automatically
-	  applicable
-	  nouns
-	  explicit
-	  outside
-	  sponsored
-	  attachment
-	  audio
-	  spied
-	  doesn't
-	  counting
-	  encoded
-	  implements
-	  recursively
-	  emailaddress
-	  arguments
-	  queuerules
-	  members
-	  priority
-	  output
-	  advanced
-	  silencethreshold
-	  brazilian
-	  debugging
-	  argument
-	  meadmin
-	  formatting
-	  integrated
-	  sneakiness
+	  ASTERISK-30126 #close
 
-	  ASTERISK-29714
+	  Change-Id: I009c4dcbf9338a13e3baf87b52a5bbe4f9f81a42
 
-	  Change-Id: Ie5ecaec91c00b26309da4e51cfc0991a5bb7d092
+2022-04-01 15:17 +0000 [8a21417095]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-10-30 20:04 +0000 [0150c3b698]  Josh Soref <jsoref@users.noreply.github.com>
+	* chan_dahdi: Add POLARITY function.
 
-	* UPGRADE.txt: Spelling fixes
+	  Adds a POLARITY function which can be used to
+	  retrieve the current polarity of an FXS channel
+	  as well as set the polarity of an FXS channel
+	  to idle or reverse at any point during a call.
 
-	  Correct typos of the following word families:
+	  ASTERISK-30000 #close
 
-	  themselves
-	  support
-	  received
+	  Change-Id: If6f50998f723e4484bf68e2473f5cedfeaf9b8f1
 
-	  ASTERISK-29714
+2022-06-01 21:03 +0000 [7cc026b3fb]  Mike Bradeen <mbradeen@sangoma.com>
 
-	  Change-Id: Ibd0a7996d5801c754d3d44fba31fe788a13dba95
+	* Makefile: Avoid git-make user conflict
 
-2021-10-30 20:04 +0000 [42d1c134f7]  Josh Soref <jsoref@users.noreply.github.com>
+	  make_version now silently checks if the required git commands will
+	  fail.  If they do, then return UNKNOWN__git_check_fail to
+	  distinguish this failure from other UNKNOWN__ version failures
 
-	* CHANGES: Spelling fixes
+	  Makefile checks for this value on install and exits out with
+	  instructions
 
-	  Correct typos of the following word families:
+	  ASTERISK-30029
 
-	  issuing
-	  execution
-	  bridging
-	  alert
-	  respective
-	  unlikely
-	  confbridge
-	  offered
-	  negotiation
-	  announced
-	  engineer
-	  systems
-	  inherited
-	  passthrough
-	  functionality
-	  supporting
-	  conflicts
-	  semantically
-	  monitor
-	  specify
-	  specifiable
+	  Change-Id: If8f10cac8f509c08981120f17555762342020221
 
-	  ASTERISK-29714
+2022-06-18 07:17 +0000 [2843e5678d]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Ia6b1cf634f52c5f7b1b8769dc54dae78106ed98c
+	* app_confbridge: Always set minimum video update interval.
 
-2021-11-08 07:00 +0000 [301647788e]  George Joseph <gjoseph@digium.com>
+	  Currently, if multiple video-enabled ConfBridges are
+	  conferenced together, we immediately get into a scenario
+	  where an infinite sequence of video updates fills up
+	  the taskprocessor queue and causes memory consumption
+	  to climb unabated until Asterisk is killed. This is due
+	  to the core bridging mechanism that provides video updates
+	  (softmix_bridge_write_control in bridge_softmix.c)
+	  continously updating all the channels in the bridge with
+	  video updates.
 
-	* CI: Rename 'master' node to 'built-in'
+	  The logic to do so in the core is that the video updates
+	  should be provided if the video_update_discard property
+	  for the bridge is 0, or if enough time has elapsed since
+	  the last video update. Thus, we already have a safeguard
+	  built in to ensure the scenario described above does not
+	  happen. Currently, however, this safeguard is not being
+	  adequately ensured.
 
-	  Jenkins renamed the 'master' node to 'built-in' in version
-	  2.319 so we have to adjust as well.
+	  In app_confbridge, the video_update_discard property
+	  defaults to 2000, which is a healthy value that should
+	  completely prevent this issue. However, this value is
+	  only set onto the bridge in the SFU video mode. This
+	  leaves video modes such as follow_talker completely
+	  vulnerable, since video_update_discard will actually
+	  be 0, since the default or set value was never applied.
+	  As a result, the core bridging mechanism will always
+	  try to provide video updates regardless of when the last
+	  one was sent.
 
-	  Change-Id: Ice663c3a66d0eedf76e8e5fe530328455991ec25
+	  To prevent this issue from happening, we now always
+	  set the video_update_discard property on the bridge
+	  with the value from the bridge profile. The app_confbridge
+	  defaults will thus ensure that infinite video updates
+	  no longer happen in any video mode.
 
-2021-11-08 08:08 +0000 [608e52c939]  Alexander Traud <pabstraud@compuserve.com>
+	  ASTERISK-29907 #close
 
-	* BuildSystem: In POSIX sh, == in place of = is undefined.
+	  Change-Id: I4accb2536ac62797950468e9930f12ef7dd486b2
 
-	  ASTERISK-29724
+2022-07-05 10:24 +0000 [d25bf55de5]  Sean Bright <sean@seanbright.com>
 
-	  Change-Id: I59aa0e52effdc16992f3a736ccf73430a6ef135b
+	* pbx.c: Simplify ast_context memory management.
 
-2021-10-24 06:55 +0000 [36c5f5e5fa]  Naveen Albert <asterisk@phreaknet.org>
+	  Allocate all of the ast_context's character data in the structure's
+	  flexible array member and eliminate the clunky fake_context. This will
+	  simplify future changes to ast_context.
 
-	* sig_analog: Fix truncated buffer copy
+	  Change-Id: I98357de75d8ac2b3c4c9f201223632e6901021ea
 
-	  Fixes compiler warning caused by a truncated copy of the ANI2 into a
-	  buffer of size 10. This could prevent the null terminator from being
-	  copied if the copy value exceeds the size of the buffer. This increases
-	  the buffer size to 101 to ensure there is no way for truncation to occur.
+2022-07-13 13:38 +0000 [80d6f5eb20]  George Joseph <gjoseph@digium.com>
 
-	  ASTERISK-29702 #close
+	* geoloc_eprofile.c: Fix setting of loc_src in set_loc_src()
 
-	  Change-Id: Ief9052212952840fa44de6463b8699fdb3e163d0
+	  line 196:    loc_src = '\0';
+	  should have been
+	  line 196:    *loc_src = '\0';
 
-2021-11-08 09:01 +0000 [e63461b008]  Sean Bright <sean.bright@gmail.com>
+	  The issue was caught by the gcc optimizer complaining that
+	  loc_src had a zero length because the pointer itself was being
+	  set to NULL instead of the _contents_ of the pointer being set
+	  to the NULL terminator.
 
-	* pbx.c: Don't remove dashes from hints on reload.
+	  ASTERISK-30138
+	  Reported-by: Sean Bright
 
-	  When reloading dialplan, hints created dynamically would lose any dash
-	  characters. Now we ignore those dashes if we are dealing with a hint
-	  during a reload.
+	  Change-Id: Id247be113cc8510f043ca053d5b4f5f3d32acd29
 
-	  ASTERISK-28040 #close
+2022-07-07 10:32 +0000 [1fa568e76f]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: I95e48f5a268efa3c6840ab69798525d3dce91636
+	* Geolocation:  chan_pjsip Capability Preview
 
-2021-10-24 07:31 +0000 [92857e70b6]  Naveen Albert <asterisk@phreaknet.org>
+	  This commit adds res_pjsip_geolocation which gives chan_pjsip
+	  the ability to use the core geolocation capabilities.
 
-	* app_voicemail: Fix phantom voicemail bug on rerecord
+	  This commit message is intentionally short because this isn't
+	  a simple capability.  See the documentation at
+	  https://wiki.asterisk.org/wiki/display/AST/Geolocation
+	  for more information.
 
-	  If users are able to press # for options while leaving
-	  a message and then press 3 to rerecord the message, if
-	  the caller hangs up during the rerecord prompt but before
-	  Asterisk starts recording a message, then an "empty"
-	  voicemail gets processed whereby an email gets sent out
-	  notifying the user of a 0:00 duration message. The file
-	  doesn't actually exist, so playback will fail since there
-	  was no message to begin with.
+	  THE CAPABILITIES IMPLEMENTED HERE MAY CHANGE BASED ON
+	  USER FEEDBACK!
 
-	  This adds a check after the streaming of the rerecord
-	  announcement to see if the caller has hung up. If so,
-	  we bail out early so that we can clean up properly.
+	  ASTERISK-30128
 
-	  ASTERISK-29391 #close
+	  Change-Id: Ie2e2bcd87243c2cfabc43eb823d4427c7086f4d9
 
-	  Change-Id: Id965d72759a2fd3b39afb76fec08aaebebe75c31
+2022-02-15 07:29 +0000 [639d72e98c]  George Joseph <gjoseph@digium.com>
 
-2021-10-25 19:47 +0000 [bea08a563b]  Naveen Albert <asterisk@phreaknet.org>
+	* Geolocation:  Core Capability Preview
 
-	* chan_iax2: Allow both secret and outkey at dial time
+	  This commit adds res_geolocation which creates the core capabilities
+	  to manipulate Geolocation information on SIP INVITEs.
 
-	  Historically, the dial syntax for IAX2 has held that
-	  an outkey (used only for RSA authenticated calls)
-	  and a secret (used only for plain text and MD5 authenticated
-	  calls, historically) were mutually exclusive, and thus
-	  the same position in the dial string was used for both
-	  values.
+	  An upcoming commit will add res_pjsip_geolocation which will
+	  allow the capabilities to be used with the pjsip channel driver.
 
-	  Now that encryption is possible with RSA authentication,
-	  this poses a limitation, since encryption requires a
-	  secret and RSA authentication requires an outkey. Thus,
-	  the dial syntax is extended so that both a secret and
-	  an outkey can be specified.
+	  This commit message is intentionally short because this isn't
+	  a simple capability.  See the documentation at
+	  https://wiki.asterisk.org/wiki/display/AST/Geolocation
+	  for more information.
 
-	  The new extended syntax is backwards compatible with the
-	  old syntax. However, a secret can now be specified after
-	  the outkey, or the outkey can be specified after the secret.
-	  This makes it possible to spawn an encrypted RSA authenticated
-	  call without a corresponding peer being predefined in iax.conf.
+	  THE CAPABILITIES IMPLEMENTED HERE MAY CHANGE BASED ON
+	  USER FEEDBACK!
 
-	  ASTERISK-29707 #close
+	  ASTERISK-30127
 
-	  Change-Id: I1f8149313ed760169d604afbb07720a8b07dd00e
+	  Change-Id: Ibfde963121b1ecf57fd98ee7060c4f0808416303
 
-2021-10-28 07:09 +0000 [95da40cd50]  Alexander Traud <pabstraud@compuserve.com>
+2022-05-31 19:49 +0000 [bcc18ca9f5]  Naveen Albert <asterisk@phreaknet.org>
 
-	* res_snmp: As build tool, prefer pkg-config over net-snmp-config.
+	* general: Fix various typos.
 
-	  ASTERISK-29709
+	  ASTERISK-30089 #close
 
-	  Change-Id: Ie169df878bdfc3a06b3097c5c38d185b480f54d4
+	  Change-Id: I1f5db911fd05a3a211c522c13e990fa1d0e62275
 
-2021-10-28 07:41 +0000 [8b76a3cd3b]  Alexander Traud <pabstraud@compuserve.com>
+2022-06-17 12:15 +0000 [4cbe12d6d1]  Morvai Szabolcs
 
-	* stasis: Avoid 'dispatched' as unused variable in normal mode.
+	* cel_odbc & res_config_odbc: Add support for SQL_DATETIME field type
 
-	  ASTERISK-29710
+	  See also: ASTERISK_30023
 
-	  Change-Id: Ia849f1172e4e694c5d5d7f0cad449f936ee12216
+	  ASTERISK-30096 #close
+	  patches:
+	    inline on issue - submitted by Morvai Szabolcs
 
-2021-10-29 10:05 +0000 [2c03f73016]  Sean Bright <sean@seanbright.com>
+	  Change-Id: I79c0b74862100acd9c8319dca5cc456a654d02eb
 
-	* various: Fix GCC 11.2 compilation issues.
+2022-07-04 05:21 +0000 [5f60caa402]  Naveen Albert <asterisk@phreaknet.org>
 
-	  * Initialize some variables that are never used anyway.
+	* chan_iax2: Allow compiling without OpenSSL.
 
-	  * Use valid pointers instead of integers cast to void pointers when
-	    calling pthread_setspecific().
+	  ASTERISK_30007 accidentally made OpenSSL a
+	  required depdendency. This adds an ifdef so
+	  the relevant code is compiled only if OpenSSL
+	  is available, since it only needs to be executed
+	  if OpenSSL is available anyways.
 
-	  ASTERISK-29711 #close
-	  ASTERISK-29713 #close
+	  ASTERISK-30083 #close
 
-	  Change-Id: I8728cd6f2f4b28e0e48113c5da450b768c2a6683
+	  Change-Id: Iad05c1a9a8bd2a48e7edf8d234eaa9f80779e34d
 
-2021-09-09 09:39 +0000 [08cb67251f]  George Joseph <gjoseph@digium.com>
+2022-06-28 06:59 +0000 [68bcf4c4c5]  Joshua C. Colp <jcolp@sangoma.com>
 
-	* ast_coredumper:  Refactor to better find things
+	* websocket / aeap: Handle poll() interruptions better.
 
-	  The search for a running asterisk when --running is used
-	  has been greatly simplified and in the event it doesn't
-	  work, you can now specify a pid to use on the command
-	  line with --pid.
+	  A sporadic test failure was happening when executing the AEAP
+	  Websocket transport tests. It was originally thought this was
+	  due to things not getting cleaned up fast enough, but upon further
+	  investigation I determined the underlying cause was poll()
+	  getting interrupted and this not being handled in all places.
 
-	  The search for asterisk modules when --tarball-coredumps
-	  is used has been enhanced to have a better chance of finding
-	  them and in the event it doesn't work, you can now specify
-	  --libdir on the command line to indicate the library directory
-	  where they were installed.
+	  This change adds EINTR and EAGAIN handling to the Websocket
+	  client connect code as well as the AEAP Websocket transport code.
+	  If either occur then the code will just go back to waiting
+	  for data.
 
-	  The DATEFORMAT variable was renamed to DATEOPTS and is now
-	  passed to the 'date' utility rather than running DATEFORMAT
-	  as a command.
+	  The originally disabled failure test case has also been
+	  re-enabled.
 
-	  The coredump and output files are now renamed with DATEOPTS.
-	  This can be disabled by specifying --no-rename.
+	  ASTERISK-30099
 
-	  Several confusing and conflicting options were removed:
-	  --append-coredumps
-	  --conffile
-	  --no-default-search
-	  --tarball-uniqueid
+	  Change-Id: I1711a331ecf5d35cd542911dc6aaa9acf1e172ad
 
-	  The script was re-structured to make it easier for follow.
+2022-05-14 16:25 +0000 [f5680a7568]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I674be64bdde3ef310b6a551d4911c3b600ffee59
+	* res_cliexec: Add dialplan exec CLI command.
 
-2021-10-21 12:29 +0000 [ae97aaedb0]  Kevin Harwell <kharwell@sangoma.com>
+	  Adds a CLI command similar to "dialplan eval function" except for
+	  applications: "dialplan exec application", useful for quickly
+	  testing certain application behavior directly from the CLI
+	  without writing any dialplan.
 
-	* strings/json: Add string delimter match, and object create with vars methods
+	  ASTERISK-30062 #close
 
-	  Add a function to check if there is an exact match a one string between
-	  delimiters in another string.
+	  Change-Id: I42e9fa9b60746c21450d40f99a026d48d2486dde
 
-	  Add a function that will create an ast_json object out of a list of
-	  Asterisk variables. An excludes string can also optionally be passed
-	  in.
+2022-07-03 16:29 +0000 [938383aff3]  Trevor Peirce <trev@acrovoice.ca>
 
-	  Also, add a macro to make it easier to get object integers.
+	* features: Update documentation for automon and automixmon
 
-	  Change-Id: I5f34f18e102126aef3997f19a553a266d70d6226
+	  The current documentation is out of date and does not reflect actual
+	  behaviour.  This change makes documentation clearer and accurately
+	  reflect the purpose of relevant channel variables.
 
-2021-09-21 12:09 +0000 [2e55c0fded]  Ben Ford <bford@digium.com>
+	  ASTERISK-30123
 
-	* STIR/SHAKEN: Option split and response codes.
+	  Change-Id: I160d0b01fce862477ad55ac1aa708a730473eb6f
 
-	  The stir_shaken configuration option now has 4 different choices to pick
-	  from: off, attest, verify, and on. Off and on behave the same way they
-	  do now. Attest will only perform attestation on the endpoint, and verify
-	  will only perform verification on the endpoint.
+2022-06-27 12:31 +0000 [5fe9887701]  George Joseph <gjoseph@digium.com>
 
-	  Certain responses are required to be sent based on certain conditions
-	  for STIR/SHAKEN. For example, if we get a Date header that is outside of
-	  the time range that is considered valid, a 403 Stale Date response
-	  should be sent. This and several other responses have been added.
+	* Geolocation: Base Asterisk Prereqs
 
-	  Change-Id: I4ac1ecf652cd0e336006b0ca638dc826b5b1ebf7
+	  * Added ast_variable_list_from_quoted_string()
+	    Parse a quoted string into an ast_variable list.
 
-2021-08-25 08:15 +0000 [a203769c9d]  Rodrigo Ramírez Norambuena <a@rodrigoramirez.com>
+	  * Added ast_str_substitute_variables_full2()
+	    Perform variable/function/expression substitution on an ast_str.
 
-	* app_queue: Add LoginTime field for member in a queue.
+	  * Added ast_strsep_quoted()
+	    Like ast_strsep except you can specify a specific quote character.
+	    Also added unit test.
 
-	  Add a time_t logintime to storage a time when a member is added into a
-	  queue.
+	  * Added ast_xml_find_child_element()
+	    Find a direct child element by name.
 
-	  Also, includes show this time (in seconds) using a 'queue show' command
-	  and the field LoginTime for response for AMI events.
+	  * Added ast_xml_doc_dump_memory()
+	    Dump the specified document to a buffer
 
-	  ASTERISK-18069 #close
+	  * ast_datastore_free() now checks for a NULL datastore
+	    before attempting to destroy it.
 
-	  Change-Id: Ied6c3a300f78d78eebedeb3e16a1520fc3fff190
+	  Change-Id: I5dcefed2f5f93a109e8b489e18d80d42e45244ec
 
-2021-10-21 12:49 +0000 [859f579504]  Kevin Harwell <kharwell@sangoma.com>
+2022-06-24 06:09 +0000 [740c773781]  Boris P. Korzun <drtr0jan@yandex.ru>
 
-	* res_speech: Add a type conversion, and new engine unregister methods
+	* pbx_lua: Remove compiler warnings
 
-	  Add a new function that converts a speech results type to a string.
-	  Also add another function to unregister an engine, but returns a
-	  pointer to the unregistered engine object instead of a success/fail
-	  integer.
+	  Improved variable definitions (specified correct type) for avoiding
+	  compiler warnings.
 
-	  Change-Id: I0f7de17cb411021c09fb03988bc2b904e1380192
+	  ASTERISK-30117 #close
 
-2021-10-07 13:07 +0000 [0b2646aee6]  Mike Bradeen <mbradeen@sangoma.com>
+	  Change-Id: I3b00c1befb658ee9379ddabd9a9132765ca9201a
 
-	* various: Fix GCC 11 compilation issues.
+2022-04-08 05:34 +0000 [d52e2b0f1d]  Jose Lopes <jose.lopes@nfon.com>
 
-	  test_voicemail_api: Use empty char* for empty_msg_ids.
-	  chan_skinny: Fix size of calledParty to be maximum extension.
-	  menuselect: Change Makefile to stop deprecated warnings. Added comments
-	  test_linkedlist: 'bogus' variable was manually allocated from a macro
-	  and the test fails if this happens but the compiler couldn't 'see' this
-	  and returns a warning. memset to all 0's after allocation.
-	  chan_ooh323: Fixed various indentation issues that triggered misleading
-	   indentation warnings.
+	* res_pjsip_header_funcs: Add functions PJSIP_RESPONSE_HEADER and PJSIP_RESPONSE_HEADERS
 
-	  ASTERISK-29682
-	  Reported by: George Joseph
+	  These new functions allow retrieving information from headers on 200 OK
+	  INVITE response.
 
-	  Change-Id: If4fe42222c8444dc16828a42731ee53b4ce5cbbe
+	  ASTERISK-29999
 
-2021-09-20 11:10 +0000 [63c8d12e95]  Shloime Rosenblum <shloimerosenblum@gmail.com>
+	  Change-Id: I264a610a9333359297a0825feb29a1bb4f4ad144
 
-	* apps/app_playback.c: Add 'mix' option to app_playback
+2022-06-09 02:10 +0000 [77f6c50814]  Boris P. Korzun <drtr0jan@yandex.ru>
 
-	  I am adding a mix option that will play by filename and say.conf unlike
-	  say option that will only play with say.conf. It
-	  will look on the format of the name, if it is like say it play with
-	  say.conf if not it will play the file name.
+	* res_prometheus: Optional load res_pjsip_outbound_registration.so
 
-	  ASTERISK-29662
+	  Switched res_pjsip_outbound_registration.so dep to optional. Added
+	  module loaded check before using it.
 
-	  Change-Id: I815816916a308f0fa8f165140dc15772dcbd547a
+	  ASTERISK-30101 #close
 
-2021-10-19 11:35 +0000 [c07e3c2f4d]  George Joseph <gjoseph@digium.com>
+	  Change-Id: Ia34f1684d984e821fbdd4de8911f930337703666
 
-	* BuildSystem: Check for alternate openssl packages
+2022-04-30 11:44 +0000 [626fefdf7d]  Naveen Albert <asterisk@phreaknet.org>
 
-	  OpenSSL is one of those packages that often have alternatives
-	  with later versions.  For instance, CentOS/EL 7 has an
-	  openssl package at version 1.0.2 but there's an openssl11
-	  package from the epel repository that has 1.1.1.  This gets
-	  installed to /usr/include/openssl11 and /usr/lib64/openssl11.
-	  Unfortunately, the existing --with-ssl and --with-crypto
-	  ./configure options expect to point to a source tree and
-	  don't work in this situation.  Also unfortunately, the
-	  checks in ./configure don't use pkg-config.
+	* app_dial: Fix dial status regression.
 
-	  In order to make this work with the existing situation, you'd
-	  have to run...
-	  ./configure --with-ssl=/usr/lib64/openssl11 \
-	      --with-crypto=/usr/lib64/openssl11 \
-	      CFLAGS=-I/usr/include/openssl11
+	  ASTERISK_28638 caused a regression by incorrectly aborting
+	  early and overwriting the status on certain calls.
+	  This was exhibited by certain technologies such as DAHDI,
+	  where DAHDI returns NULL for the request if a line is busy.
+	  This caused the BUSY condition to be incorrectly treated
+	  as CHANUNAVAIL because the DIALSTATUS was getting incorrectly
+	  overwritten and call handling was aborted early.
 
-	  BUT...  those options don't get passed down to bundled pjproject
-	  so when you run make, you have to include the CFLAGS again
-	  which is a big pain.
+	  This is fixed by instead checking if any valid peers have been
+	  specified, as opposed to checking the list size of successful
+	  requests. This is because the latter could be empty but this
+	  does not indicate any kind of problem. This restores the
+	  previous working behavior.
 
-	  Oh...  To make matters worse, although you can specify
-	  PJPROJECT_CONFIGURE_OPTS on the ./configure command line,
-	  they don't get saved so if you do a make clean, which will
-	  force a re-configure of bundled pjproject, those options
-	  don't get used.
+	  ASTERISK-29989 #close
 
-	  So...
+	  Change-Id: I4d4b209b967816b1bc791534593ababa2b99bb88
 
-	  * In configure.ac... Since pkg-config is installed by install_prereq
-	    anyway, we now use it to check for the system openssl >= 1.1.0.
-	    If that works, great.  If not, we check for the openssl11
-	    package. If that works, great.  If not, we fall back to just
-	    checking for any openssl.  If pkg-config isn't installed for some
-	    reason, or --with-ssl=<dir> or --with-crypto=<dir> were specified
-	    on the ./configure command line, we fall back to the existing
-	    logic that uses AST_EXT_LIB_CHECK().
+2022-04-01 14:49 +0000 [350ffcb02b]  Naveen Albert <asterisk@phreaknet.org>
 
-	  * The whole OpenSSL check process has been moved up before
-	    THIRD_PARTY_CONFIGURE(), which does the initial pjproject
-	    bundled configure, is run.  This way the results of the above
-	    checks, which may result in new include or library directories,
-	    is included.
+	* db: Notify user if deleted DB entry didn't exist.
 
-	  * Although not strictly needed for openssl, We now save the value of
-	    PJPROJECT_CONFIGURE_OPTS in the makeopts file so it can be used
-	    again if a re-configure is triggered.
+	  Currently, if using the CLI to delete a DB entry,
+	  "Database entry removed" is always returned,
+	  regardless of whether or not the entry actually
+	  existed in the first place. This meant that users
+	  were never told if entries did not exist.
 
-	  ASTERISK-29693
+	  The same issue occurs if trying to delete a DB key
+	  using AMI.
 
-	  Change-Id: I341ab7603e6b156aa15a66f43675ac5029d5fbde
+	  To address this, new API is added that is more stringent
+	  in deleting values from AstDB, which will not return
+	  success if the value did not exist in the first place,
+	  and will print out specific error details if available.
 
-2021-10-14 14:38 +0000 [8c2720e540]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK-30001 #close
 
-	* func_talkdetect.c: Fix logical errors in silence detection.
+	  Change-Id: Ic84e3eddcd66c7a6ed7fea91cdfd402568378b18
 
-	  There are 3 separate changes here:
+2022-02-05 15:16 +0000 [b841845453]  Naveen Albert <asterisk@phreaknet.org>
 
-	  1. The documentation erroneously stated that the dsp_talking_threshold
-	     argument was a number of milliseconds when it is actually an energy
-	     level used by the DSP code to classify talking vs. silence.
+	* cli: Fix CLI blocking forever on terminating backslash
 
-	  2. Fixes a copy paste error in the argument handling code.
+	  A corner case exists in CLI parsing where if
+	  a CLI user in a remote console ends with
+	  a backslash and then invokes command completion
+	  (using TAB or ?), then the console will freeze
+	  forever until a SIGQUIT signal is sent to the
+	  process, due to getting blocked forever
+	  reading the command completion. CTRL+C
+	  and other key combinations have no impact on
+	  the CLI session.
 
-	  3. Don't erroneously switch to the talking state if we aren't actively
-	     handling a frame we've classified as talking.
+	  This occurs because, in such cases, the CLI
+	  process is waiting for AST_CLI_COMPLETE_EOF
+	  to appear in the buffer from the main process,
+	  but instead the main process is confused by
+	  the funny syntax and thus prints out the CLI help.
+	  As a result, the CLI process is stuck on the
+	  read call, waiting for the completion that
+	  will never come.
 
-	  Patch inspired by one provided by Moritz Fain (License #6961).
+	  This prevents blocking forever by checking
+	  if the data from the main process starts with
+	  "Usage:". If it does, that means that CLI help
+	  was sent instead of the tab complete vector,
+	  and thus the CLI should bail out and not wait
+	  any longer.
 
-	  ASTERISK-27816 #close
+	  ASTERISK-29822 #close
 
-	  Change-Id: I5953fd570b98b49c41cee55bfe3b941753fb2511
+	  Change-Id: I9810ac59304fec162da701653c9c834f0ec8f670
 
-2021-10-14 10:15 +0000 [e3466893e9]  Sebastien Duthil <sduthil@wazo.community>
+2022-06-18 12:13 +0000 [ae8a36a7d9]  Naveen Albert <asterisk@phreaknet.org>
 
-	* main/stun.c: fix crash upon STUN request timeout
+	* app_dial: Propagate outbound hook flashes.
 
-	  Some ast_stun_request users do not provide a destination address when
-	  sending to a connection-mode socket.
+	  The Dial application currently stops hook flashes
+	  dead in their tracks from propagating through on
+	  outbound calls. This fixes that so they can go
+	  down the wire.
 
-	  ASTERISK-29691
+	  ASTERISK-30115 #close
 
-	  Change-Id: Idd9114c3380216ba48abfc3c68619e79ad37defc
+	  Change-Id: Id4e78b29a049f35c5b1e7520eaa10d0eb5b7f97c
 
-2021-10-12 13:17 +0000 [bac66e9743]  Mike Bradeen <mbradeen@sangoma.com>
+2022-06-20 16:00 +0000 [e5553fbd15]  Naveen Albert <asterisk@phreaknet.org>
 
-	* build: prevent binary downloads for non x86 architectures
+	* res_calendar_icalendar: Send user agent in request.
 
-	  download_externals: Add check for i686 and i386 (in addition
-	  to the current x86_64) and exit if not one of the three.
+	  Microsoft recently began rejecting all requests for
+	  ICS calendars on Office 365 with 400 errors if
+	  the request doesn't contain a user agent. See:
 
-	  ASTERISK-26497
+	  https://docs.microsoft.com/en-us/answers/questions/883904/34the-remote-server-returned-an-error-400-bad-requ.html
 
-	  Change-Id: Ia4d429fcefa5b2f5b6e99159d4607de8e8325b2f
+	  Accordingly, we now send a user agent on requests for
+	  ICS files so that requests to Office 365 will work as
+	  they did before.
 
-2021-10-11 14:04 +0000 [482281deff]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK-30106
 
-	* configure: Remove unused OpenSSL SRTP check.
+	  Change-Id: Ie9dcaef12ae8adf37533c684499eb11005fac8f7
 
-	  Discovered while looking at ASTERISK~29684. Usage was removed in change
-	  I3c77c7b00b2ffa2e935632097fa057b9fdf480c0.
+2022-05-21 20:40 +0000 [0f0cc43e1b]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Iaf2f7a16ea5a7eee6375319347e4b40b8e7b10e3
+	* say: Abort play loop if caller hangs up.
 
-2021-10-13 10:26 +0000  Asterisk Development Team <asteriskteam@digium.com>
+	  If the caller has hung up, break out of the play loop so we don't try
+	  to play remaining files and fail to do so.
 
-	* asterisk 18.8.0-rc1 Released.
+	  ASTERISK-30075 #close
 
-2021-10-13 05:21 +0000 [9063680148]  Asterisk Development Team <asteriskteam@digium.com>
+	  Change-Id: I55e85be28ee90b48c0fe4ce20ac136a7dbb49f14
 
-	* Update CHANGES and UPGRADE.txt for 18.8.0
-2021-10-07 12:50 +0000 [804b1987fb]  Sean Bright <sean.bright@gmail.com>
+2022-06-08 18:32 +0000 [a3b2daf127]  Kevin Harwell <kharwell@sangoma.com>
 
-	* Makefile: Use basename in a POSIX-compliant way.
+	* res_pjsip: allow TLS verification of wildcard cert-bearing servers
 
-	  If you aren't using GNU coreutils, chances are that your basename
-	  doesn't know about the -s argument. Luckily for us, basename does what
-	  we need it do even without the -s argument.
+	  Rightly the use of wildcards in certificates is disallowed in accordance
+	  with RFC5922. However, RFC2818 does make some allowances with regards to
+	  their use when using subject alt names with DNS name types.
 
-	  Change-Id: I8b81a429bb037b997ee6640ff8a2b5e860962bb7
+	  As such this patch creates a new setting for TLS transports called
+	  'allow_wildcard_certs', which when it and 'verify_server' are both enabled
+	  allows DNS name types, as well as the common name that start with '*.'
+	  to match as a wildcard.
 
-2021-10-05 19:59 +0000 [e091aa2763]  Mark Murawski <markm@intellasoft.net>
+	  For instance: *.example.com
+	  will match for: foo.example.com
 
-	* pbx_ael:  Fix crash and lockup issue regarding 'ael reload'
+	  Partial matching is not allowed, e.g. f*.example.com, foo.*.com, etc...
+	  And the starting wildcard only matches for a single level.
 
-	  Avoid infinite recursion and crash
+	  For instance: *.example.com
+	  will NOT match for: foo.bar.example.com
 
-	  Change-Id: I8ed05ec3aa2806c50c77edc5dd0cd4e4fa08b3f4
+	  The new setting is disabled by default.
 
-2021-05-24 13:04 +0000 [437b2bfbd6]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-30072 #close
 
-	* chan_iax2: Add encryption for RSA authentication
+	  Change-Id: If0be3fdab2e09c2a66bb54824fca406ebaac3da4
 
-	  Adds support for encryption to RSA-authenticated
-	  calls. Also prevents crashes if an RSA IAX2 call
-	  is initiated to a switch requiring encryption
-	  but no secret is provided.
+2022-05-15 07:41 +0000 [4a11ae7ecf]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-20219
+	* pbx: Add helper function to execute applications.
 
-	  Change-Id: I18f1f9d7c59b4f9cffa00f3b94a4c875846efd40
+	  Finding an application and executing it if found is
+	  a common task throughout Asterisk. This adds a helper
+	  function around pbx_exec to do this, to eliminate
+	  redundant code and make it easier for modules to
+	  substitute variables and execute applications by name.
 
-2021-07-19 11:34 +0000 [15e432220c]  Matthew Kern <mkern@alconconstruction.com>
+	  ASTERISK-30061 #close
 
-	* res_pjsip_t38: bind UDPTL sessions like RTP
+	  Change-Id: Ifee4d2825df7545fb515d763d393065675140c84
 
-	  In res_pjsip_sdp_rtp, the bind_rtp_to_media_address option and the
-	  fallback use of the transport's bind address solve problems sending
-	  media on systems that cannot send ipv4 packets on ipv6 sockets, and
-	  certain other situations. This change extends both of these behaviors
-	  to UDPTL sessions as well in res_pjsip_t38, to fix fax-specific
-	  problems on these systems, introducing a new option
-	  endpoint/t38_bind_udptl_to_media_address.
+2022-05-10 07:19 +0000 [d052418b94]  Stanislav Abramenkov <stas.abramenkov@gmail.com>
 
-	  ASTERISK-29402
+	* pjsip: Upgrade bundled version to pjproject 2.12.1
 
-	  Change-Id: I87220c0e9cdd2fe9d156846cb906debe08c63557
+	  More information:
+	  https://github.com/pjsip/pjproject/releases/tag/2.12.1
 
-2021-09-29 12:58 +0000 [5a6f140765]  Naveen Albert <asterisk@phreaknet.org>
+	  Pull request to third-party
+	  https://github.com/asterisk/third-party/pull/11
 
-	* app_read: Fix null pointer crash
+	  ASTERISK-30050
 
-	  If the terminator character is not explicitly specified
-	  and an indications tone is used for reading a digit,
-	  there is no null pointer check so Asterisk crashes.
-	  This prevents null usage from occuring.
+	  Change-Id: Icb4e86d4b85ef9b975355c91f3ed56a50b51c6bd
 
-	  ASTERISK-29673 #close
+2022-06-11 14:29 +0000 [2604a8352b]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Ie941833e123c3dbfb88371b5de5edbbe065514ac
+	* asterisk.c: Fix incompatibility warnings for remote console.
 
-2021-09-29 04:32 +0000 [0ab4e7491d]  Jean Aunis <jean.aunis@prescom.fr>
+	  A previous review fixing ASTERISK_22246 and ASTERISK_26582
+	  got a couple of the options mixed up as to whether or not
+	  they are compatible with the remote console. This fixes
+	  those to the best of my knowledge.
 
-	* res_rtp_asterisk: fix memory leak
+	  ASTERISK-30097 #close
 
-	  Add missing reference decrement in rtp_deallocate_transport()
+	  Change-Id: Id54166991aa79f04fb02699cc499bedda854253b
 
-	  ASTERISK-29671
+2022-06-07 16:03 +0000 [d9ce2a652b]  Kevin Harwell <kharwell@sangoma.com>
 
-	  Change-Id: I8d22dbedb90e8dade0829b7a28372f404b07caa9
+	* test_aeap_transport: disable part of failing unit test
 
-2021-09-19 15:08 +0000 [29c44caecb]  Shloime Rosenblum <shloimerosenblum@gmail.com>
+	  The 'transport_binary' test sporadically fails, but on a theory that the
+	  problem is caused by a previously executed test, transport_connect_fail,
+	  part of that test has been disabled until a solution is found.
 
-	* main/say.c: Support future dates with Q and q format params
+	  ASTERISK_30099
 
-	  The current versions do not support future dates in all say application when using the 'Q' or 'q' format parameter and says "today" for everything that is greater than today
+	  Change-Id: I48ed74d696aa9b6159f59661f3d535cac4c909e1
 
-	  ASTERISK-29637
+2022-05-13 07:33 +0000 [97f278a94a]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I1fb1cef0ce3c18d87b1fc94ea309d13bc344af02
+	* sig_analog: Fix broken three-way conferencing.
 
-2021-07-21 16:36 +0000 [4368764032]  Joseph Nadiv <ynadiv@corpit.xyz>
+	  Three-way calling for analog lines is currently broken.
+	  If party A is on a call with party B and initiates a
+	  three-way call to party C, the behavior differs depending
+	  on whether the call is conferenced prior to party C
+	  answering. The post-answer case is correct. However,
+	  if A flashes before C answers, then the next flash
+	  disconnects B rather than C, which is incorrect.
 
-	* res_pjsip_registrar: Remove unavailable contacts if exceeds max_contacts
+	  This error occurs because the subs are not swapped
+	  in the misbehaving case. This is because the flash
+	  handler only swaps the subs if C has answered already,
+	  which is wrong. To fix this, we swap the subs regardless
+	  of whether C has answered or not when the call is
+	  conferenced. This ensures that C is disconnected
+	  on the next hook flash, rather than B as can happen
+	  currently.
 
-	  The behavior of max_contacts and remove_existing are connected.  If
-	  remove_existing is enabled, the soonest expiring contacts are removed.
-	  This may occur when there is an unavailable contact.  Similarly,
-	  when remove_existing is not enabled, registrations from good
-	  endpoints are rejected in favor of retaining unavailable contacts.
+	  ASTERISK-30043 #close
 
-	  This commit adds a new AOR option remove_unavailable, and the effect
-	  of this setting will depend on remove_existing.  If remove_existing
-	  is set to no, we will still remove unavailable contacts when they
-	  exceed max_contacts, if there are any. If remove_existing is set to
-	  yes, we will prioritize the removal of unavailable contacts before
-	  those that are expiring soonest.
+	  Change-Id: I96c5bf6c9b7eb2636136b716c677c82c079b6f06
 
-	  ASTERISK-29525
+2022-05-15 08:31 +0000 [cc8e098e1d]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Ia2711b08f2b4d1177411b1be23e970d7fdff5784
+	* app_voicemail: Add option to prevent message deletion.
 
-2021-09-23 09:13 +0000 [ea36473c45]  Joshua C. Colp <jcolp@sangoma.com>
+	  Adds an option to VoiceMailMain that prevents the user
+	  from deleting messages during that application invocation.
+	  This can be useful for public or shared mailboxes, where
+	  some users should be able to listen to messages but not
+	  delete them.
 
-	* ari: Ignore invisible bridges when listing bridges.
+	  ASTERISK-30063 #close
 
-	  When listing bridges we go through the ones present in
-	  ARI, get their snapshot, turn it into JSON, and add it
-	  to the payload we ultimately return.
+	  Change-Id: Icdfb8423ae8d1fce65a056b603eb84a672e80a26
 
-	  An invisible "dial bridge" exists within ARI that would
-	  also try to be added to this payload if the channel
-	  "create" and "dial" routes were used. This would ultimately
-	  fail due to invisible bridges having no snapshot
-	  resulting in the listing of bridges failing.
+2022-05-31 05:59 +0000 [ddc2cca659]  Naveen Albert <asterisk@phreaknet.org>
 
-	  This change makes it so that the listing of bridges
-	  ignores invisible ones.
+	* res_parking: Add music on hold override option.
 
-	  ASTERISK-29668
+	  An m option to Park and ParkAndAnnounce now allows
+	  specifying a music on hold class override.
 
-	  Change-Id: I14fa4b589b4657d1c2a5226b0f527f45a0cd370a
+	  ASTERISK-30087
 
-2021-09-19 06:14 +0000 [484da42d6c]  Naveen Albert <asterisk@phreaknet.org>
+	  Change-Id: I03de8d97b100e451b2611b5a621d48750f5d6a9e
 
-	* func_vmcount: Add support for multiple mailboxes
+2022-05-31 20:43 +0000 [51d262af12]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Allows multiple mailboxes to be specified for VMCOUNT
-	  instead of just one.
+	* xmldocs: Improve examples.
 
-	  ASTERISK-29661 #close
+	  Use example tags instead of regular para tags
+	  where possible.
 
-	  Change-Id: I9108528300795fd5b607efa9d4dd7b74be031813
+	  ASTERISK-30090
 
-2021-09-21 09:58 +0000 [e98839b73c]  Sean Bright <sean.bright@gmail.com>
+	  Change-Id: Iada8bbfda08f30b118cedf2d040bbb21e4966ec5
 
-	* message.c: Support 'To' header override with AMI's MessageSend.
+2022-03-12 12:27 +0000 [31dc28ab09]  Naveen Albert <asterisk@phreaknet.org>
 
-	  The MessageSend AMI action has been updated to allow the Destination
-	  and the To addresses to be provided separately. This brings the
-	  MessageSend manager command in line with the capabilities of the
-	  MessageSend dialplan application.
+	* res_pjsip_outbound_registration: Make max random delay configurable.
 
-	  ASTERISK-29663 #close
+	  Currently, PJSIP will randomly wait up to 10 seconds for each
+	  outbound registration's initial attempt. The reason for this
+	  is to avoid having all outbound registrations attempt to register
+	  simultaneously.
 
-	  Change-Id: I8513168d3e189a9fed88aaab6f5547ccb50d332c
+	  This can create limitations with the test suite where we need to
+	  be able to receive inbound calls potentially within 10 seconds of
+	  starting up. For instance, we might register to another server
+	  and then try to receive a call through the registration, but if
+	  the registration hasn't happened yet, this will fail, and hence
+	  this inconsistent behavior can cause tests to fail. Ultimately,
+	  this requires a smaller random value because there may be no good
+	  reason to wait for up to 10 seconds in these circumstances.
 
-2021-09-15 13:21 +0000 [cf0d656ae6]  Naveen Albert <asterisk@phreaknet.org>
+	  To address this, a new config option is introduced which makes this
+	  maximum delay configurable. This allows, for instance, this to be
+	  set to a very small value in test systems to ensure that registrations
+	  happen immediately without an unnecessary delay, and can be used more
+	  generally to control how "tight" the initial outbound registrations
+	  are.
 
-	* func_channel: Add CHANNEL_EXISTS function.
+	  ASTERISK-29965 #close
 
-	  Adds a function to check for the existence of a channel by
-	  name or by UNIQUEID.
+	  Change-Id: Iab989a8e94323e645f3a21cbb6082287c7b2f3fd
 
-	  ASTERISK-29656 #close
+2022-06-07 16:53 +0000 [5f0581c5f5]  Trevor Peirce <trev@acrovoice.ca>
 
-	  Change-Id: Ib464e9eb6e13dc683a846286798fecff4fd943cb
+	* res_pjsip: Actually enable session timers when timers=always
 
-2021-09-05 13:11 +0000 [cfd0246d11]  Naveen Albert <asterisk@phreaknet.org>
+	  When a pjsip endpoint is defined with timers=always, this has been a
+	  functional noop.  This patch correctly sets the feature bitmap to both
+	  enable support for session timers and to enable them even when the
+	  endpoint itself does not request or support timers.
 
-	* app_queue: Fix hint updates for included contexts
+	  ASTERISK-29603
+	  Reported-By: Ray Crumrine
 
-	  Previously, if custom hints were used with the hint:
-	  format in app_queue, when device state changes occured,
-	  app_queue would only do a literal string comparison of
-	  the context used for the hint in app_queue and the context
-	  of the hint which just changed state. This caused hints
-	  to not update and become stale if the context associated
-	  with the agent included the context which actually changes
-	  state, essentially completely breaking device state for
-	  any such agents defined in this manner.
+	  Change-Id: I8b5eeaa9ec7f50cc6d96dd34c2b4aa9c53fb5440
 
-	  This fix adds an additional check to ensure that included
-	  contexts are also compared against the context which changed
-	  state, so that the behavior is correct no matter whether the
-	  context is specified to app_queue directly or indirectly.
+2022-06-06 17:21 +0000 [044a08ae7b]  Alexei Gradinari <alex2grad@gmail.com>
 
-	  ASTERISK-29578 #close
+	* res_pjsip_pubsub: delete scheduled notification on RLS update
 
-	  Change-Id: I8caf2f8da8157ef3d9ea71a8568c1eec95592b78
+	  If there is scheduled notification, we must delete it
+	  to avoid using destroyed subscriptions.
 
-2021-09-10 09:40 +0000 [b2c834e349]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK-29906
 
-	* res_http_media_cache.c: Compare unaltered MIME types.
+	  Change-Id: I1c644e5e15a8fe43eed8e4f9112f113cbf87a40f
 
-	  Rather than stripping parameters from Content-Type headers before
-	  comparison, first try to compare the whole string. If no match is
-	  found, strip the parameters and try that way.
+2022-06-07 08:48 +0000 [355c07e2e6]  Alexei Gradinari <alex2grad@gmail.com>
 
-	  ASTERISK-29275 #close
+	* res_pjsip_pubsub: XML sanitized RLS display name
 
-	  Change-Id: I2963c8ecbb3a9605b78b6421c415108d77a66a0f
+	  ASTERISK-29891
 
-2021-07-25 17:19 +0000 [a65bb134f5]  Naveen Albert <asterisk@phreaknet.org>
+	  Change-Id: Ic8c9697e616446e06e6302653eae902aa23372ad
 
-	* logger: Add custom logging capabilities
+2022-06-01 14:59 +0000 [74df01009f]  Christof Efkemann <christof@efkemann.net>
 
-	  Adds the ability for users to log to custom log levels
-	  by providing custom log level names in logger.conf. Also
-	  adds a logger show levels CLI command.
+	* app_sayunixtime: Use correct inflection for German time.
 
-	  ASTERISK-29529
+	  In function ast_say_date_with_format_de(), take special
+	  care when the hour is one o'clock. In this case, the
+	  German number "eins" must be inflected to its neutrum form,
+	  "ein". This is achieved by playing "digits/1N" instead of
+	  "digits/1". Fixes both 12- and 24-hour formats.
 
-	  Change-Id: If082703cf81a436ae5a565c75225fa8c0554b702
+	  ASTERISK-30092
 
-2021-09-17 10:57 +0000 [dce142baa4]  Sean Bright <sean.bright@gmail.com>
+	  Change-Id: Ica9b80125c0b317e378d89c1ea786816e2635510
 
-	* app_externalivr.c: Fix mixed leading whitespace in source code.
+2022-05-16 08:01 +0000 [169e553320]  Naveen Albert <asterisk@phreaknet.org>
 
-	  No functional changes.
+	* chan_iax2: Prevent deadlock due to duplicate autoservice.
 
-	  Change-Id: I46514152c0af67f395526374aaa847ccd6a85378
+	  If a switch is invoked using chan_iax2, deadlock can result
+	  because the PBX core is autoservicing the channel while chan_iax2
+	  also then attempts to service it while waiting for the result
+	  of the switch. This removes servicing of the channel to prevent
+	  any conflicts.
 
-2021-09-17 14:58 +0000 [03377c35fc]  Guido Falsi <madpilot@freebsd.org>
+	  ASTERISK-30064 #close
 
-	* res_rtp_asterisk.c: Fix build failure when not building with pjproject.
+	  Change-Id: Ie92f206d32f9a36924af734ddde652b21106af22
 
-	  Some code has been added referencing symbols defined in a block
-	  protected by #ifdef HAVE_PJPROJECT. Protect those code parts in
-	  ifdef blocks too.
+2022-05-03 07:44 +0000 [3e8629454a]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29660
+	* loader: Prevent deadlock using tab completion.
 
-	  Change-Id: Ib18d4392d51ac80ca5481dabf6e498a4e3e49e6f
+	  If tab completion using ast_module_helper is attempted
+	  during startup, deadlock will ensue because the CLI
+	  will attempt to lock the module list while it is already
+	  locked by the loader. This causes deadlock because when
+	  the loader tries to acquire the CLI lock, they are blocked
+	  on each other.
 
-2021-09-16 13:43 +0000  Asterisk Development Team <asteriskteam@digium.com>
+	  Waiting for startup to complete is not feasible because
+	  the CLI lock is acquired while waiting, so deadlock will
+	  ensure regardless of whether or not a lock on the module
+	  list is attempted.
 
-	* asterisk 18.7.0-rc1 Released.
+	  To prevent deadlock, we immediately abort if tab completion
+	  is attempted on the module list before Asterisk is fully
+	  booted.
 
-2021-09-16 08:39 +0000 [00cf86dafe]  Asterisk Development Team <asteriskteam@digium.com>
+	  ASTERISK-30039 #close
 
-	* Update CHANGES and UPGRADE.txt for 18.7.0
-2021-09-13 10:18 +0000 [e8f7b53023]  Carlos Oliva <carlos.oliva@invoxcontact.com>
+	  Change-Id: Idd468906c512bb196631e366a8f597a0e2e9271d
 
-	* app_mp3: Force output to 16 bits in mpg123
+2022-03-23 06:05 +0000 [64a764c33e]  Naveen Albert <asterisk@phreaknet.org>
 
-	  In new mpg123 versions (since 1.26) the default output is 32 bits
-	  Asterisk expects the output in 16 bits, so we force the output to be on 16 bits.
-	  It will work wit new and old versions of mpg123.
-	  Thanks Thomas Orgis <thomas-forum@orgis.org> for giving the key!
+	* res_calendar: Prevent assertion if event ends in past.
 
-	  ASTERISK-29635 #close
+	  res_calendar will trigger an assertion currently
+	  if the ending time is calculated to be in the past.
+	  Unlike the reminder and start times, however, there
+	  is currently no check to catch non-positive times
+	  and set them to 1. As a result, if we get a negative
+	  value by happenstance, this can cause a crash.
 
-	  Change-Id: I88c7740118b5af4e895bd8b765b68ed5c11fc816
+	  To prevent the assertion from begin triggered, we now
+	  use the same logic as the reminder and start events
+	  to catch this issue before it can cause a problem.
 
-2021-09-14 12:02 +0000 [0947c30224]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-29981 #close
 
-	* pjproject: Add patch to fix trailing whitespace issue in rtpmap
+	  Change-Id: Idfb3204d195f350d2575fb4bc72a54a597d6e93c
 
-	  An issue was found where a particular manufacturer's phones add a
-	  trailing space to the end of the rtpmap attribute when specifying
-	  a payload type that has a "param" after the format name and clock
-	  rate. For example:
+2022-05-30 15:55 +0000 [bae8092826]  Naveen Albert <asterisk@phreaknet.org>
 
-	  a=rtpmap:120 opus/48000/2 \r\n
+	* res_parking: Warn if out of bounds parking spot requested.
 
-	  Because pjmedia_sdp_attr_get_rtpmap currently takes everything after
-	  the second '/' up to the line end as the param, the space is
-	  included in future comparisons, which then fail if the param being
-	  compared to doesn't also have the space.
+	  Emits a warning if the user has requested a parking spot that
+	  is out of bounds for the requested parking lot.
 
-	  We now use pj_scan_get() to parse the param part of rtpmap so
-	  trailing whitespace is automatically stripped.
+	  ASTERISK-30086
 
-	  ASTERISK-29654
+	  Change-Id: I1080371e4f63e94724455003753014fbd3f95fbf
 
-	  Change-Id: Ibd0a4e243a69cde7ba9312275b13ab62ab86bc1b
+2022-05-19 09:23 +0000 [a03b53bb7b]  Maximilian Fridrich <m.fridrich@commend.com>
 
-2021-06-08 15:44 +0000 [1a23c9c047]  Naveen Albert <asterisk@phreaknet.org>
+	* chan_pjsip: Only set default audio stream on hold.
 
-	* res_pjsip_caller_id: Add ANI2/OLI parsing
+	  When a PJSIP channel is set on hold or off hold, all streams were set
+	  on/off hold. This is not the desired behaviour and caused issues
+	  when there were multiple streams in the topology.
 
-	  Adds parsing of ANI II digits (Originating
-	  Line Information) to PJSIP, on par with
-	  what currently exists in chan_sip.
+	  Now, only the default audio stream is set on/off hold when a hold is
+	  indicated.
 
-	  ASTERISK-29472
+	  ASTERISK-30051
 
-	  Change-Id: Ifc938a7a7d45ce33999ebf3656a542226f6d3847
+	  Change-Id: I04f1110565fd05fea565f5539b534b54549d4f71
 
-2021-06-28 10:37 +0000 [60daa8f761]  Naveen Albert <asterisk@phreaknet.org>
+2022-05-26 16:29 +0000 [42b191ad64]  Alexei Gradinari <alex2grad@gmail.com>
 
-	* app_mf: Add channel agnostic MF sender
+	* res_pjsip_dialog_info_body_generator: Set LOCAL target URI as local URI
 
-	  Adds a SendMF application and PlayMF manager
-	  event to send arbitrary R1 MF tones on the
-	  current or specified channel.
+	  The change "Add LOCAL/REMOTE tags in dialog-info+xml" set both "local"
+	  Identity Element URI and Target Element URI to the same value -
+	  the channel Caller Number.
+	  For Identity Element it's ok to set as Caller ID.
+	  But Local Target URI should be set as local URI.
 
-	  ASTERISK-29496
+	  In this case the Local Target URI can be used for Directed Call Pickup
+	  by Polycom ip-phones (parameter useLocalTargetUriforLegacyPickup).
 
-	  Change-Id: I5d89afdbccee3f86cc702ed96d882f3d351327a4
+	  Also XML sanitized Display names.
 
-2021-09-10 09:56 +0000 [847349853a]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK-24601
 
-	* test_http_media_cache.c: Fix copy/paste error during test deregistration.
+	  Change-Id: If130a2f2f3b2339b14dca0ec0ebeea3a87b34343
 
-	  Change-Id: I9a3a978b2f818be464e062d97b93831b127ef28c
+2022-05-11 14:48 +0000 [7dcea19ce8]  Shloime Rosenblum <shloimerosenblum@gmail.com>
 
-2021-09-02 18:20 +0000 [c736cef310]  Naveen Albert <asterisk@phreaknet.org>
+	* res_agi: Evaluate dialplan functions and variables in agi exec if enabled
 
-	* app_stack: Include current location if branch fails
+	  Agi commnad exec can now evaluate dialplan functions and
+	  variables if variable AGIEXECFULL is set to yes. this can
+	  be useful when executing Playback or Read from agi.
 
-	  Previously, the error emitted when app_stack tries
-	  to branch to a dialplan location that doesn't exist
-	  has included only the information about the attempted
-	  branch in the error log. This adds the current location
-	  as well so users can see where the branch failed in
-	  the logs.
+	  ASTERISK-30058 #close
 
-	  ASTERISK-29626
+	  Change-Id: I669991f540496e7bddd096fec82b52c083036832
 
-	  Change-Id: Ia23502ab2ad21485a1ac74295063a8f25a6df5ce
+2022-05-17 12:01 +0000 [a6c7524e0d]  Sean Bright <sean@seanbright.com>
 
-2021-09-03 13:27 +0000 [d9747104ff]  Sungtae Kim <pchero21@gmail.com>
+	* ast_pkgconfig.m4: AST_PKG_CONFIG_CHECK() relies on sed.
 
-	* resource_channels.c: Fix external media data option
+	  Make sure that we have a working sed before trying to use it.
 
-	  Fixed the external media creation handle to handle the 'data' option correctly.
+	  ASTERISK-30059 #close
 
-	  ASTERISK-29629
+	  Change-Id: I9abad67a5df11b665d480feec304ab9d6f55cc76
 
-	  Change-Id: I22e57fe8ebf3d3e08fb2121aa4a8a52cc62e8129
+2022-04-25 17:40 +0000 [4bf2473ac4]  Moritz Fain <moritz@fain.io>
 
-2021-09-02 18:57 +0000 [6198c1d28c]  Naveen Albert <asterisk@phreaknet.org>
+	* ari: expose channel driver's unique id to ARI channel resource
 
-	* func_strings: Add STRBETWEEN function
+	  This change exposes the channel driver's unique id (i.e. the Call-ID
+	  for chan_sip/chan_pjsip based channels) to ARI channel resources
+	  as `protocol_id`.
 
-	  Adds the STRBETWEEN function, which can be used to insert a
-	  substring between each character in a string. For instance,
-	  this can be used to insert pauses between DTMF tones in a
-	  string of digits.
+	  ASTERISK-30027
+	  Reported by: Moritz Fain
+	  Tested by: Moritz Fain
 
-	  ASTERISK-29627
+	  Change-Id: I7cc6e7a9d29efe74bc27811d788dac20fe559b87
 
-	  Change-Id: Ice23009d4a8e9bb9718d2b2301d405567087d258
+2022-05-17 09:18 +0000 [8d7819482c]  Sean Bright <sean@seanbright.com>
 
-2021-09-08 14:29 +0000 [ee62a07914]  Sean Bright <sean.bright@gmail.com>
+	* loader.c: Use portable printf conversion specifier for int64.
 
-	* test_abstract_jb.c: Fix put and put_out_of_order memory leaks.
+	  ASTERISK-30060 #close
 
-	  We can't rely on RAII_VAR(...) to properly clean up data that is
-	  allocated within a loop.
+	  Change-Id: I88d47a1488be2f39017b8d562f993f081844fcb8
 
-	  ASTERISK-27176 #close
+2022-05-17 07:18 +0000 [63ff0ccadf]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  Change-Id: Ib575616101230c4f603519114ec62ebf3936882c
+	* res_pjsip_transport_websocket: Also set the remote name.
 
-2021-09-02 19:00 +0000 [19de228e8b]  Naveen Albert <asterisk@phreaknet.org>
+	  As part of PJSIP 2.11 a behavior change was done to require
+	  a matching remote hostname on an established transport for
+	  secure transports. Since the Websocket transport is considered
+	  a secure transport this caused the existing connection to not
+	  be found and used.
 
-	* func_env: Add DIRNAME and BASENAME functions
+	  We now set the remote hostname and the transport can be found.
 
-	  Adds the DIRNAME and BASENAME functions, which are
-	  wrappers around the corresponding C library functions.
-	  These can be used to safely and conveniently work with
-	  file paths and names in the dialplan.
+	  ASTERISK-30065
 
-	  ASTERISK-29628 #close
+	  Change-Id: Ia1cdef33e1411f927985b4b852c95e163c080e94
 
-	  Change-Id: Id3aeb907f65c0ff96b6e57751ff0cb49d61db7f3
+2022-05-04 10:37 +0000 [4848d6eeb9]  Thomas Guebels <tgu@escaux.com>
 
-2021-07-26 12:46 +0000 [b6b7b1490b]  Naveen Albert <asterisk@phreaknet.org>
+	* res_pjsip_transport_websocket: save the original contact host
 
-	* func_sayfiles: Retrieve say file names
+	  This is needed to be able to restore it in REGISTER responses,
+	  otherwise the client won't be able to find the contact it created.
 
-	  Up until now, all of the logic used to translate
-	  arguments to the Say applications has been
-	  directly coupled to playback, preventing other
-	  modules from using this logic.
+	  ASTERISK-30042
 
-	  This refactors code in say.c and adds a SAYFILES
-	  function that can be used to retrieve the file
-	  names that would be played. These can then be
-	  used in other applications or for other purposes.
+	  Change-Id: I0c5823918199acf09246b3b206fbde66773688f6
 
-	  Additionally, a SayMoney application and a SayOrdinal
-	  application are added. Both SayOrdinal and SayNumber
-	  are also expanded to support integers greater than
-	  one billion.
+2022-01-07 10:25 +0000 [604785f931]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29531
+	* res_pjsip_outbound_registration: Show time until expiration
 
-	  Change-Id: If9718c89353b8e153d84add3cc4637b79585db19
+	  Adjusts the pjsip show registration(s) commands to show
+	  the amount of seconds remaining until a registration
+	  expires.
 
-2021-08-09 12:41 +0000 [a6eb1b6f95]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-29845 #close
 
-	* res_tonedetect: Tone detection module
+	  Change-Id: Ic4fea15a1a1056c424416def49d1ca8e776c0483
 
-	  dsp.c contains arbitrary tone detection functionality
-	  which is currently only used for fax tone recognition.
-	  This change makes this functionality publicly
-	  accessible so that other modules can take advantage
-	  of this.
+2022-04-29 11:42 +0000 [432a1d2d7e]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Additionally, a WaitForTone and TONE_DETECT app and
-	  function are included to allow users to do their
-	  own tone detection operations in the dialplan.
+	* app_confbridge: Add function to retrieve channels.
 
-	  ASTERISK-29546
+	  Adds the CONFBRIDGE_CHANNELS function which can be used
+	  to retrieve a comma-separated list of channels, filtered
+	  by a particular type of participant category. This output
+	  can then be used with functions like UNSHIFT, SHIFT, POP,
+	  etc.
 
-	  Change-Id: Ie38c395000f4fd4d04e942e8658e177f8f499b26
+	  ASTERISK-30036 #close
 
-2021-09-08 09:36 +0000 [2806a45034]  George Joseph <gjoseph@digium.com>
+	  Change-Id: I1950aff932437476dc1abab6f47fb4ac90520b83
 
-	* res_snmp: Add -fPIC to _ASTCFLAGS
+2022-04-26 14:00 +0000 [a24979a2d7]  Naveen Albert <asterisk@phreaknet.org>
 
-	  With gcc 11, res/res_snmp.c and res/snmp/agent.c need the
-	  -fPIC option added to its _ASTCFLAGS.
+	* chan_dahdi: Fix broken operator mode clearing.
 
-	  ASTERISK-29634
+	  Currently, the operator services mode in DAHDI is broken and unusable.
+	  The actual operator recall functionality works properly; however,
+	  when the operator hangs up (which is the only way that such a call
+	  is allowed to end), both lines are permanently taken out of service
+	  until "dahdi restart" is run. This prevents this feature from being
+	  used.
 
-	  Change-Id: I34649c85e075fd954e578378fabf798c3f038f50
+	  Operator mode is one of the few factors that can cause the general
+	  analog event handling in sig_analog not to be used. Several years
+	  back, much of the analog handling was moved from chan_dahdi to
+	  sig_analog. However, this was not done fully or consistently at
+	  the time, and when operator mode is active, sig_analog does not
+	  get used. Generally this is correct, but in the case of hangup
+	  it should be using sig_analog regardless of the operator mode;
+	  otherwise, the lines do not properly clear and they become unusable.
 
-2021-09-04 12:07 +0000 [858cb386fd]  Sean Bright <sean.bright@gmail.com>
+	  This bug is fixed so the operator can now hang up and properly
+	  release the call. It is treated just like any other hangup. The
+	  operator mode functionality continues to work as it did before.
 
-	* term.c: Add support for extended number format terminfo files.
+	  ASTERISK-29993 #close
 
-	  ncurses 6.1 introduced an extended number format for terminfo files
-	  which the terminfo parsing in Asterisk is not able to parse. This
-	  results in some TERM values that do support color (screen-256color on
-	  Ubuntu 20.04 for example) to not get a color console.
+	  Change-Id: Ib2e3ddb40d9c71e8801e0b4bb0a12e2b52f51d24
 
-	  ASTERISK-29630 #close
+2022-05-03 07:57 +0000 [4aa541683b]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: I27a4fcfab502219924af2d6b1c46feba92903cb3
+	* GCC12: Fixes for 16+
 
-2021-09-07 12:32 +0000 [347e9a7e4d]  Sean Bright <sean.bright@gmail.com>
+	  Most issues were in stringfields and had to do with comparing
+	  a pointer to an constant/interned string with NULL.  Since the
+	  string was a constant, a pointer to it could never be NULL so
+	  the comparison was always "true".  gcc now complains about that.
 
-	* app_voicemail.c: Ability to silence instructions if greeting is present.
+	  There were also a few issues where determining if there was
+	  enough space for a memcpy or s(n)printf which were fixed
+	  by defining some of the involved variables as "volatile".
 
-	  There is an option to silence voicemail instructions but it does not
-	  take into consideration if a recorded greeting exists or not. Add a
-	  new 'S' option that does that.
+	  There were also a few other miscellaneous fixes.
 
-	  ASTERISK-29632 #close
+	  ASTERISK-30044
 
-	  Change-Id: I03f2f043a9beb9d99deab302247e2a8686066fb4
+	  Change-Id: Ia081ca1bcfb329df6487c4660aaf1944309eb570
 
-2021-09-03 00:30 +0000 [c1a575907b]  Jasper Hafkenscheid <jasper.hafkenscheid@wearespindle.com>
+2022-05-04 13:00 +0000 [49108810d1]  George Joseph <gjoseph@digium.com>
 
-	* res_srtp: Disable parsing of not enabled cryptos
+	* GCC12: Fixes for 18+.  state_id_by_topic comparing wrong value
 
-	  When compiled without extended srtp crypto suites also disable parsing
-	  these from received SDP. This prevents using these, as some client
-	  implementations are not stable.
+	  GCC 12 caught an issue in state_id_by_topic where we were
+	  checking a pointer for NULL instead of the contents of
+	  the pointer for '\0'.
 
-	  ASTERISK-29625
+	  ASTERISK-30044
 
-	  Change-Id: I7dafb29be1cdaabdc984002573f4bea87520533a
+	  Change-Id: Ia0b04d4fff45c92acb7f07132a33622fa341148e
 
-2021-09-06 11:37 +0000 [689c703b2c]  Sean Bright <sean.bright@gmail.com>
+2022-04-29 03:47 +0000 [8fdc6008a4]  Maximilian Fridrich <m.fridrich@commend.com>
 
-	* dns.c: Load IPv6 DNS resolvers if configured.
+	* core_unreal: Flip stream direction of second channel.
 
-	  IPv6 nameserver addresses are stored in different part of the
-	  __res_state structure, so look there if we appear to have support for
-	  it.
+	  When a new unreal (local) channel is created, a second (;2) channel is
+	  created as a counterpart which clones the topology of the first
+	  channel. This creates issues when an outgoing stream is sendonly or
+	  recvonly as the stream state of the inbound channel will be the same
+	  as the stream state of the outbound channel.
 
-	  ASTERISK-28004 #close
+	  Now the stream state is flipped for the streams of the 2nd channel in
+	  ast_unreal_new_channels if the outgoing stream topology is recvonly or
+	  sendonly.
 
-	  Change-Id: I67067077d8a406ee996664518d9c8fbf11f6977d
+	  ASTERISK-29655
+	  Reported by: Michael Auracher
 
-2021-09-08 07:52 +0000 [de19836c24]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-29638
+	  Reported by: Michael Auracher
 
-	* bridge_softmix: Suppress error on topology change failure
+	  Change-Id: I0cea29635bb20b7bf7fd0fb95498cd44dab98fbf
 
-	  There are conditions under which a failure to change topology
-	  is expected so there's no need to print an ERROR message.
+2022-03-27 07:33 +0000 [892c06564f]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29618
-	  Reported by: Alexander
+	* chan_dahdi: Document dial resource options.
 
-	  Change-Id: Idc168b8588e018bf3a23769f08c4ad646086d481
+	  Documents the Dial syntax for DAHDI, namely the channel group,
+	  distinctive ring, answer confirmation, and digital call options
+	  that are specified in the resource itself.
 
-2021-08-31 02:50 +0000 [479cc17f45]  sungtae kim <sungtae.kim@avoxi.com>
+	  ASTERISK-24827 #close
 
-	* resource_channels.c: Fix wrong external media parameter parse
+	  Change-Id: Ib95e78497fb00dc5cbfde1c93a69f034bfd08c30
 
-	  Fixed ARI external media handler to accept body parameters.
+2022-03-29 18:47 +0000 [0a8b3d3467]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29622
+	* chan_dahdi: Don't allow MWI FSK if channel not idle.
 
-	  Change-Id: I49509c48a6cbc0fb4165bfa4f834b5e8b9ace20d
+	  For lines that have mailboxes configured on them, with
+	  FSK MWI, DAHDI will periodically try to dispatch FSK
+	  to update MWI. However, this is never supposed to be
+	  done when a channel is not idle.
 
-2021-08-25 10:21 +0000 [5c836c8e36]  Sean Bright <sean.bright@gmail.com>
+	  There is currently an edge case where MWI FSK can
+	  extraneously get spooled for the channel if a caller
+	  hook flashes and hangs up, which triggers a recall ring.
+	  After one ring, the on hook time threshold in this if
+	  condition has been satisfied and an MWI update is spooled.
+	  This means that when the phone is picked up again, the
+	  answerer gets an FSK spill before being reconnected to
+	  the party on hold.
 
-	* config_options: Handle ACO arrays correctly in generated XML docs.
+	  To prevent this, we now explicitly check to ensure that
+	  subchannel 0 has no owner. There is no owner when DAHDI
+	  channels are idle, but if the channel is "in use" in some
+	  way (such as in the aforementioned scenario), then there
+	  is an owner, and we shouldn't process MWI at this time.
 
-	  There are 3 separate changes here but they are all closely related:
+	  ASTERISK-28518 #close
 
-	  * Only try to set matchfield attributes on 'field' nodes
+	  Change-Id: Ia3904434fd81688d71742f7e84358b7e1c38e92a
 
-	  * We need to adjust how we treat the category pointer based on the
-	    value of the category_match, to avoid memory corruption. We now
-	    generate a regex-like string when match types other than
-	    ACO_WHITELIST and ACO_BLACKLIST are used.
+2022-02-23 10:29 +0000 [a2679b0ee2]  Michael Cargile <mikec@vicidial.com>
 
-	  * Switch app_agent_pool from ACO_BLACKLIST_ARRAY to
-	    ACO_BLACKLIST_EXACT since we only have one category we need to
-	    ignore, not two.
+	* apps/confbridge: Added hear_own_join_sound option to control who hears sound_join
 
-	  ASTERISK-29614 #close
+	  Added the hear_own_join_sound option to the confbridge user profile to
+	  control who hears the sound_join audio file. When set to 'yes' the user
+	  entering the conference and the participants already in the conference
+	  will hear the sound_join audio file. When set to 'no' the user entering
+	  the conference will not hear the sound_join audio file, but the
+	  participants already in the conference will hear the sound_join audio
+	  file.
 
-	  Change-Id: I7be7bdb1bb9814f942bc6bb4fdd0a55a7b7efe1e
+	  ASTERISK-29931
+	  Added by Michael Cargile
 
-2021-08-18 14:44 +0000 [5a685249ce]  Naveen Albert <asterisk@phreaknet.org>
+	  Change-Id: I856bd66dc0dfa057323860a6418c1371d249abd2
 
-	* chan_iax2: Add ANI2/OLI information element
+2022-03-27 06:23 +0000 [19c841950b]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Adds an information element for ANI2 so that
-	  Originating Line Information can be transmitted
-	  over IAX2 channels.
+	* chan_dahdi: Don't append cadences on dahdi restart.
 
-	  ASTERISK-29605 #close
+	  Currently, if any custom ring cadences are specified, they are
+	  appended to the array of cadences from wherever we left off
+	  last time. This works properly the first time, but on subsequent
+	  dahdi restarts, it means that the existing cadences are left
+	  alone and (most likely) the same cadences are then re-added
+	  afterwards. In short order, the cadence array gets maxed out
+	  and the user begins seeing warnings that the array is full
+	  and no more cadences may be added.
 
-	  Change-Id: Iaeacdf6ccde18eaff7f776a0f49fee87dcb549d2
+	  This buggy behavior persists until Asterisk is completely
+	  restarted; however, if and when dahdi restart is run again,
+	  then the same problem is reintroduced.
 
-2021-08-31 15:03 +0000 [042ae05be7]  Mark Murawski <markm@intellasoft.net>
+	  This fixes this behavior so that cadence parsing is more
+	  idempotent, that is so running dahdi restart multiple times
+	  starts adding cadences from the beginning, rather than from
+	  wherever the last cadence was added.
 
-	* pbx_ael:  Fix crash and lockup issue regarding 'ael reload'
+	  As before, it is still not possible to revert to the default
+	  cadences by simply removing all cadences in this manner, nor
+	  is it possible to delete existing cadences. However, this
+	  does make it possible to update existing cadences, which
+	  was not possible before, and also ensures that the cadences
+	  remain unchanged if the config remains unchanged.
 
-	  Currently pbx_ael does not check if a reload is currently pending
-	  before proceeding with a reload. This can cause multiple threads to
-	  operate at the same time on what should be mutex protected data. This
-	  change adds protection to reloading to ensure only one ael reload is
-	  executing at a time.
+	  ASTERISK-29990 #close
 
-	  ASTERISK-29609 #close
+	  Change-Id: Ie32ea3e8a243b766756b1afce684d4a31ee7421d
 
-	  Change-Id: I5ed392ad226f6e4e7696ad742076d3e45c57af35
+2022-04-02 16:22 +0000 [fbe960ca42]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-08-25 06:49 +0000 [dd980e00b4]  Naveen Albert <asterisk@phreaknet.org>
+	* chan_iax2: Prevent crash if dialing RSA-only call without outkey.
 
-	* app_read: Allow reading # as a digit
+	  Currently, if attempting to place a call to a peer that only allows
+	  RSA authentication, if we fail to provide an outkey when placing
+	  the call, Asterisk will crash.
 
-	  Allows for the digit # to be read as a digit,
-	  just like any other DTMF digit, as opposed to
-	  forcing it to be used as an end of input
-	  indicator. The default behavior remains
-	  unchanged.
+	  This exposes the broader issue that IAX2 is prone to causing a crash
+	  if encryption or decryption is attempted but we never initialized
+	  the encryption and decryption keys. In other words, if the logic
+	  to use encryption in chan_iax2 is not perfectly aligned with the
+	  decision to build keys in the first place, then a crash is not
+	  only possible but probable. This was demonstrated by ASTERISK_29264,
+	  for instance.
 
-	  ASTERISK-18454 #close
+	  This permanently prevents such events from causing a crash by explicitly
+	  checking that keys are initialized properly before setting the flags
+	  to use encryption for the call. Instead of crashing, the call will
+	  now abort.
 
-	  Change-Id: I3033432adb9d296ad227e76b540b8b4a2417665b
+	  ASTERISK-30007 #close
 
-2021-04-05 14:06 +0000 [ac492f2ff8]  Sebastien Duthil <sduthil@wazo.community>
+	  Change-Id: If925c3d86099ceac7f621804f2532baac5050c9a
 
-	* res_rtp_asterisk: Automatically refresh stunaddr from DNS
+2022-02-05 09:03 +0000 [fe6f7dcb13]  Naveen Albert <asterisk@phreaknet.org>
 
-	  This allows the STUN server to change its IP address without having to
-	  reload the res_rtp_asterisk module.
+	* menuselect: Don't erroneously recompile modules.
 
-	  The refresh of the name resolution occurs first when the module is
-	  loaded, then recurringly, slightly after the previous DNS answer TTL
-	  expires.
+	  A bug in menuselect can cause modules that are disabled
+	  by default to be recompiled every time a recompilation
+	  occurs. This occurs for module categories that are NOT
+	  positive output, as for these categories, the modules
+	  contained in the makeopts file indicate modules which
+	  should NOT be selected. The existing procedure of iterating
+	  through these modules to mark modules as present is thus
+	  insufficient. This has led to modules with a default_enabled
+	  tag of "no" to get deleted and recompiled every time, even
+	  when they haven't changed.
 
-	  ASTERISK-29508 #close
+	  To fix this, we now modify the mark as present behavior
+	  for module categories that are not positive output. For
+	  these, we start by iterating through the module tree
+	  and marking all modules as present, then go back and
+	  mark anything contained in the makeopts file as not
+	  present. This ensures that makeopt selections are actually
+	  used properly, regardless of whether a module category
+	  uses positive output or not.
 
-	  Change-Id: I7955a046293f913ba121bbd82153b04439e3465f
+	  ASTERISK-29728 #close
 
-2021-08-24 20:04 +0000 [e660a2c03b]  Naveen Albert <asterisk@phreaknet.org>
+	  Change-Id: Idf2974c4ed8d0ba3738a92f08a6082b234277b95
 
-	* bridge_basic: Change warning to verbose if transfer cancelled
+2022-03-31 10:44 +0000 [b90650d8f4]  Naveen Albert <asterisk@phreaknet.org>
 
-	  The attended transfer feature will emit a warning if the user
-	  cancels the transfer or the attended transfer doesn't complete
-	  for any reason. Changes the warning to a verbose message,
-	  since nothing is actually wrong here.
+	* app_meetme: Don't erroneously set global variables.
 
-	  ASTERISK-29612 #close
+	  The admin_exec function in app_meetme is used by the SLA
+	  applications for internal bridging. However, in these cases,
+	  chan is NULL. Currently, this function will set some status
+	  variables that are intended for a channel, but since channel
+	  is NULL, this is erroneously creating meaningless global
+	  variables, which shouldn't be happening. This sets these
+	  variables only if chan is not NULL.
 
-	  Change-Id: I64c93cdb21360a0a8d45e9cb6db3af8168f66e6d
+	  ASTERISK-30002 #close
 
-2021-08-20 15:35 +0000 [c7af46995e]  Naveen Albert <asterisk@phreaknet.org>
+	  Change-Id: I817df6c26f5bda131678e56791b0b61ba64fc6f7
 
-	* app_queue: Don't reset queue stats on reload
+2022-03-05 05:43 +0000 [4585a9c3b8]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Prevents reloads of app_queue from also resetting
-	  queue statistics.
+	* asterisk.c: Warn of incompatibilities with remote console.
 
-	  Also preserves individual queue agent statistics
-	  if we're just reloading members.
+	  Some command line options to Asterisk only apply when Asterisk
+	  is started and cannot be used with remote console mode. If a
+	  user tries to use any of these, they are currently simply
+	  silently ignored.
 
-	  ASTERISK-28701
+	  This prints out a warning if incompatible options are used,
+	  informing users that an option used cannot be used with remote
+	  console mode. Additionally, some clarifications are added to
+	  the help text and man page.
 
-	  Change-Id: Ib5d4cdec175e44de38ef0f6ede4a7701751766f1
+	  ASTERISK-22246
+	  ASTERISK-26582
 
-2021-08-25 09:23 +0000 [82d6bd7ec9]  Alexander Traud <pabstraud@compuserve.com>
+	  Change-Id: I980a5380ef2c19e8ea348596396d5382893c4337
 
-	* res_rtp_asterisk: sqrt(.) requires the header math.h.
+2022-03-14 20:41 +0000 [306ce09df2]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29616
+	* func_db: Add function to return cardinality at prefix
 
-	  Change-Id: I6c01623926bf10ccac32612687a50fdab3ba0900
+	  Adds the DB_KEYCOUNT function, which can be used to retrieve
+	  the number of keys at a given prefix in AstDB.
 
-2021-08-25 09:29 +0000 [8410afc7ab]  Alexander Traud <pabstraud@compuserve.com>
+	  ASTERISK-29968 #close
 
-	* dialplan: Add one static and fix two whitespace errors.
+	  Change-Id: Ib2393b77b7e962dbaae6192f8576bc3f6ba92d09
 
-	  Change-Id: Ia14d515ab63e773097adc6af772ca7123a392f83
+2022-03-29 19:22 +0000 [fe50f049c4]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-06-19 23:36 +0000 [241686f860]  Sarah Autumn <sarah@connectionsmuseum.org>
+	* chan_dahdi: Fix insufficient array size for round robin.
 
-	* sig_analog: Changes to improve electromechanical signalling compatibility
+	  According to chan_dahdi.conf, up to 64 groups (numbered
+	  0 through 63) can be used when dialing DAHDI channels.
 
-	  This changeset is intended to address compatibility issues encountered
-	  when interfacing Asterisk to electromechanical telephone switches that
-	  implement ANI-B, ANI-C, or ANI-D.
+	  However, currently dialing round robin with a group number
+	  greater than 31 fails because the array for the round robin
+	  structure is only size 32, instead of 64 as it should be.
 
-	  In particular the behaviours that this impacts include:
+	  This fixes that so the round robin array size is consistent
+	  with the actual groups capacity.
 
-	   - FGC-CAMA did not work at all when using MF signaling. Modified the
-	     switch case block to send calls to the correct part of the
-	     signaling-handling state machine.
+	  ASTERISK-29994
 
-	   - For FGC-CAMA operation, the delay between called number ST and
-	     second wink for ANI spill has been made configurable; previously
-	     all calls were made to wait for one full second.
+	  Change-Id: I4caa08d7025f78ac75a0539f71aaf3eb3e85b3b7
 
-	   - After the ANI spill, previous behavior was to require a 'ST' tone
-	     to advance the call.  This has been changed to allow 'STP' 'ST2P'
-	     or 'ST3P' as well, for compatibility with ANI-D.
+2022-02-26 03:07 +0000 [a3abc868db]  Mark Petersen <bugs.digium.com@zombie.dk>
 
-	   - Store ANI2 (ANI INFO) digits in the CALLERID(ANI2) channel variable.
+	* chan_sip.c Session timers get removed on UPDATE
 
-	   - For calls with an ANI failure, No. 1 Crossbar switches will send
-	     forward a single-digit failure code, with no calling number digits
-	     and no ST pulse to terminate the spill.  I've made the ANI timeout
-	     configurable so to reduce dead air time on calls with ANI fail.
+	  If Asterisk receives a SIP REFER with Session-Timers UAC
+	  maintain Session-Timers when sending UPDATE"
 
-	   - ANI info digits configurable.  Modern digital switches will send 2
-	     digits, but ANI-B sends only a single info digit.  This caused the
-	     ANI reported by Asterisk to be misaligned.
+	  ASTERISK-29843
 
-	   - Changed a confusing log message to be more informative.
+	  Change-Id: I8e9a21c13bf757fa34d778f49ba3cf859b29ae5c
 
-	  ASTERISK-29518
+2021-06-21 07:49 +0000 [6ddb0ec939]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Ib7e27d987aee4ed9bc3663c57ef413e21b404256
+	* func_evalexten: Extension evaluation function.
 
-2021-08-05 11:55 +0000 [eb486db3af]  Andre Barbosa <andre.emanuel.barbosa@gmail.com>
+	  This adds the EVAL_EXTEN function, which may be used to retrieve
+	  the variable-substituted data at any extension.
 
-	* media_cache: Don't lock when curl the remote file
+	  ASTERISK-29486
 
-	  When playing a remote sound file, which is not in cache, first we need
-	  to download it with ast_bucket_file_retrieve.
+	  Change-Id: Iad81019689674c9f4ac77d235f5d7234adbb1432
 
-	  This can take a while if the remote host is slow. The current CURL
-	  timeout is 180secs, so in extreme situations, it can take 3 minutes to
-	  return.
+2022-02-28 19:29 +0000 [ce7846e658]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Because ast_media_cache_retrieve has a lock on all function, while we
-	  are waiting for the delayed download, Asterisk is not able to play any
-	  more files, even the files already cached locally.
+	* file.c: Prevent formats from seeking negative offsets.
 
-	  ASTERISK-29544 #close
+	  Currently, if a user uses an application like ControlPlayback
+	  to try to rewind a file past the beginning, this can throw
+	  warnings when the file format (e.g. PCM) tries to seek to
+	  a negative offset.
 
-	  Change-Id: I8d4142b463ae4a1d4c41bff2bf63324821567408
+	  Instead of letting file formats try (and fail) to seek a
+	  negative offset, we instead now catch this in the rewind
+	  function to ensure that we never seek an offset less than 0.
+	  This prevents legitimate user actions from triggering warnings
+	  from any particular file formats.
 
-2021-08-16 08:25 +0000 [b72425b1f0]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-29943 #close
 
-	* res_pjproject: Allow mapping to Asterisk TRACE level
+	  Change-Id: Ia53f2623f57898f4b8e5c894b968b01e95426967
 
-	  Allow mapping pjproject log messages to the Asterisk TRACE
-	  log level.  The defaults were also changes to log pjproject
-	  levels 3,4 to DEBUG and 5,6 to TRACE.  Previously 3,4,5,6
-	  all went to DEBUG.
+2022-02-26 06:37 +0000 [193b7a81fe]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29582
+	* chan_pjsip: Add ability to send flash events.
 
-	  Change-Id: I859a37a8dec263ed68099709cfbd3e665324c72d
+	  PJSIP currently is capable of receiving flash events
+	  and converting them to FLASH control frames, but it
+	  currently lacks support for doing the reverse: taking
+	  a FLASH control frame and converting it into a flash
+	  event in the SIP domain.
 
-2021-08-12 16:02 +0000 [dffc5e7f5c]  Naveen Albert <asterisk@phreaknet.org>
+	  This adds the ability for PJSIP to process flash control
+	  frames by converting them into the appropriate SIP INFO
+	  message, which can then be sent to the peer. This allows,
+	  for example, flash events to be sent between Asterisk
+	  systems using PJSIP.
 
-	* app_milliwatt: Timing fix
+	  ASTERISK-29941 #close
 
-	  The Milliwatt application uses incorrect tone timings
-	  that cause it to play the 1004 Hz tone constantly.
+	  Change-Id: I1590221a4d238597f79672fa5825dd4a920c94dd
 
-	  This adds an option to enable the correct timing
-	  behavior, so that the Milliwatt application can
-	  be used for milliwatt test lines. The default behavior
-	  remains unchanged for compatability reasons, even
-	  though it is incorrect.
+2021-12-26 15:39 +0000 [92d408f293]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29575 #close
+	* cli: Add command to evaluate dialplan functions.
 
-	  Change-Id: I73ccc6c6fcaa31931c6fff3b85ad1805b2ce9d8c
+	  Adds the dialplan eval function commands to evaluate a dialplan
+	  function from the CLI. The return value and function result are
+	  printed out and can be used for testing or debugging.
 
-2021-06-28 09:25 +0000 [c52ef4ac79]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-29820 #close
 
-	* func_math: Return integer instead of float if possible
+	  Change-Id: I833e97ea54c49336aca145330a2adeebfad05209
 
-	  The MIN, MAX, and ABS functions all support float
-	  arguments, but currently return floats even if the
-	  arguments are all integers and the response is
-	  a whole number, in which case the user is likely
-	  expecting an integer. This casts the float to an integer
-	  before printing into the response buffer if possible.
+2022-02-25 14:58 +0000 [0c70d497bc]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29495
+	* documentation: Adds versioning information.
 
-	  Change-Id: I902d29eacf3ecd0f8a6a5e433c97f0421d205488
+	  Adds version information for applications, functions,
+	  and manager events/actions.
 
-2021-08-04 09:46 +0000 [9cac1c16da]  Naveen Albert <asterisk@phreaknet.org>
+	  This is not completely exhaustive by any means but
+	  covers most new things added that have release
+	  versioning information in the issue tracker.
 
-	* app_morsecode: Add American Morse code
+	  ASTERISK-29940 #close
 
-	  Previously, the Morsecode application only supported international
-	  Morse code. This adds support for American Morse code and adds an
-	  option to configure the frequency used in off intervals.
+	  Change-Id: I506401e93c799715dbbe97c0a8ba18af2bf5e131
 
-	  Additionally, the application checks for hangup between tones
-	  to prevent application execution from continuing after hangup.
+2022-04-02 17:38 +0000 [bce722e60d]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29541
+	* samples: Remove obsolete sample configs.
 
-	  Change-Id: I172431a2e18e6527d577e74adfb05b154cba7bd4
+	  Removes a couple sample config files for modules
+	  which have since been removed from Asterisk.
 
-2021-08-04 14:16 +0000 [3eec5b8c5c]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-30008 #close
 
-	* func_scramble: Audio scrambler function
+	  Change-Id: I6be89cafc6c575d98a5315e4912b61a833aacf52
 
-	  Adds a function to scramble audio on a channel using
-	  whole spectrum frequency inversion. This can be used
-	  as a privacy enhancement with applications like
-	  ChanSpy or other potentially sensitive audio.
+2022-02-21 07:23 +0000 [1cdaeb8161]  Mark Petersen <bugs.digium.com@zombie.dk>
 
-	  ASTERISK-29542
+	* chan_pjsip: add allow_sending_180_after_183 option
 
-	  Change-Id: I01020769d91060a1f56a708eb405f87648d1a67e
+	  added new global config option "allow_sending_180_after_183"
+	  that if enabled will preserve 180 after a 183
 
-2021-08-04 19:28 +0000 [cb1dfecc11]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-29842
 
-	* app_originate: Add ability to set codecs
+	  Change-Id: I8a53f8c35595b6d16d8e86e241b5f110d92f3d18
 
-	  A list of codecs to use for dialplan-originated calls can
-	  now be specified in Originate, similar to the ability
-	  in call files and the manager action.
+2022-03-07 08:11 +0000 [eab489b22e]  Mark Petersen <bugs.digium.com@zombie.dk>
 
-	  Additionally, we now default to just using the slin codec
-	  for originated calls, rather than all the slin* codecs up
-	  through slin192, which has been known to cause issues
-	  and inconsistencies from AMI and call file behavior.
+	* chan_sip: SIP route header is missing on UPDATE
 
-	  ASTERISK-29543
+	  if Asterisk need to send an UPDATE before answer
+	  on a channel that uses Record-Route:
+	  it will not include a Route header
 
-	  Change-Id: I96a1aeb83d54b635b7a51e1b4680f03791622883
+	  ASTERISK-29955
 
-2021-08-16 11:11 +0000 [a8e8b3aaff]  Alexander Traud <pabstraud@compuserve.com>
+	  Change-Id: Id1920ecbfea7739a038b14dc94487ecfe7b57eef
 
-	* BuildSystem: Remove two dead exceptions for compiler Clang.
+2022-04-25 18:39 +0000 [f6062b17cc]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  Commit 305ce3d added -Wno-parentheses-equality to Makefile.rules,
-	  turning the previous two warning suppressions from commit e9520db
-	  redundant. Let us remove the latter.
+	* manager: Terminate session on write error.
 
-	  Change-Id: I0b471254b31e6e05902062761dded4b3e626c7ac
+	  On a write error to an AMI session a flag was set to
+	  indicate that the write error had occurred, with the
+	  expected result being that the session be terminated.
+	  This was not actually happening and instead writing
+	  would continue to be attempted.
 
-2021-08-10 12:41 +0000 [121860e3f6]  Sean Bright <sean.bright@gmail.com>
+	  This change adds a check for the write error and causes
+	  the session to actually terminate.
 
-	* mgcp: Remove dead debug code
+	  ASTERISK-29948
 
-	  ASTERISK-20339 #close
+	  Change-Id: Icaf5d413d4c0d5dc78292a17287fecc8720a31a5
 
-	  Change-Id: I36f364aaa1971241d8f3ea1a5909b463d185a2d5
+2022-04-21 09:10 +0000 [e9355e66d1]  Yury Kirsanov <y.kirsanov@gmail.com>
 
-2021-08-11 06:15 +0000 [13fd0789a2]  Joshua C. Colp <jcolp@sangoma.com>
+	* bridge_simple.c: Unhold channels on join simple bridge.
 
-	* policy: Add deprecation and removal versions to modules.
+	  Patch provided inline by Yury Kirsanov on the linked issue and
+	  approved by Josh Colp.
 
-	  app_meetme is deprecated in 19, to be removed in 21.
-	  app_osplookup is deprecated in 19, to be removed in 21.
-	  chan_alsa is deprecated in 19, to be removed in 21.
-	  chan_mgcp is deprecated in 19, to be removed in 21.
-	  chan_skinny is deprecated in 19, to be removed in 21.
-	  res_pktccops is deprecated in 19, to be removed in 21.
-	  cdr_mysql was deprecated in 1.8, to be removed in 19.
-	  app_mysql was deprecated in 1.8, to be removed in 19.
-	  app_ices was deprecated in 16, to be removed in 19.
-	  app_macro was deprecated in 16, to be removed in 21.
-	  app_fax was deprecated in 16, to be removed in 19.
-	  app_url was deprecated in 16, to be removed in 19.
-	  app_image was deprecated in 16, to be removed in 19.
-	  app_nbscat was deprecated in 16, to be removed in 19.
-	  app_dahdiras was deprecated in 16, to be removed in 19.
-	  cdr_syslog was deprecated in 16, to be removed in 19.
-	  chan_oss was deprecated in 16, to be removed in 19.
-	  chan_phone was deprecated in 16, to be removed in 19.
-	  chan_sip was deprecated in 17, to be removed in 21.
-	  chan_nbs was deprecated in 16, to be removed in 19.
-	  chan_misdn was deprecated in 16, to be removed in 19.
-	  chan_vpb was deprecated in 16, to be removed in 19.
-	  res_config_sqlite was deprecated in 16, to be removed in 19.
-	  res_monitor was deprecated in 16, to be removed in 21.
-	  conf2ael was deprecated in 16, to be removed in 19.
-	  muted was deprecated in 16, to be removed in 19.
+	  ASTERISK-29253 #close
 
-	  ASTERISK-29548
-	  ASTERISK-29549
-	  ASTERISK-29550
-	  ASTERISK-29551
-	  ASTERISK-29552
-	  ASTERISK-29553
-	  ASTERISK-29554
-	  ASTERISK-29555
-	  ASTERISK-29557
-	  ASTERISK-29558
-	  ASTERISK-29559
-	  ASTERISK-29560
-	  ASTERISK-29561
-	  ASTERISK-29562
-	  ASTERISK-29563
-	  ASTERISK-29564
-	  ASTERISK-29565
-	  ASTERISK-29566
-	  ASTERISK-29567
-	  ASTERISK-29568
-	  ASTERISK-29569
-	  ASTERISK-29570
-	  ASTERISK-29571
-	  ASTERISK-29572
-	  ASTERISK-29573
-	  ASTERISK-29574
+	  Change-Id: I5b9ccc67ebf06e875ed061d9e7fc21f47b0a4e1f
 
-	  Change-Id: Ic3bee31a10d42c4b3bbc913d893f7b2a28a27131
+2021-06-18 12:54 +0000 [272bac70dd]  Kevin Harwell <kharwell@sangoma.com>
 
-2021-08-12 11:00 +0000 [288d018fb7]  Asterisk Development Team <asteriskteam@digium.com>
+	* res_aeap & res_speech_aeap: Add Asterisk External Application Protocol
 
-	* Update CHANGES and UPGRADE.txt for 18.6.0
-2021-06-16 15:30 +0000 [118d848238]  Naveen Albert <asterisk@phreaknet.org>
+	  Add framework to connect to, and read and write protocol based
+	  messages from and to an external application using an Asterisk
+	  External Application Protocol (AEAP). This has been divided into
+	  several abstractions:
 
-	* func_frame_drop: New function
+	   1. transport - base communication layer (currently websocket only)
+	   2. message - AEAP description and data (currently JSON only)
+	   3. transaction - links/binds requests and responses
+	   4. aeap - transport, message, and transaction handler/manager
 
-	  Adds function to selectively drop specified frames
-	  in the TX or RX direction on a channel, including
-	  control frames.
+	  This patch also adds an AEAP implementation for speech to text.
+	  Existing speech API callbacks for speech to text have been completed
+	  making it possible for Asterisk to connect to a configured external
+	  translator service and provide audio for STT. Results can also be
+	  received from the external translator, and made available as speech
+	  results in Asterisk.
 
-	  ASTERISK-29478
+	  Unit tests have also been created that test the AEAP framework, and
+	  also the speech to text implementation.
 
-	  Change-Id: I8147c9d55d74e2e48861edba6b22f930920541ec
+	  ASTERISK-29726 #close
 
-2021-08-02 12:33 +0000 [0b1a629ecd]  Alexander Traud <pabstraud@compuserve.com>
+	  Change-Id: Iaa4b259f84aa63501e5fd2a6fb107f900b4d4ed2
 
-	* aelparse: Accept an included context with timings.
+2022-04-13 08:12 +0000 [53a3af6321]  Maximilian Fridrich <m.fridrich@commend.com>
 
-	  With Asterisk 1.6.0, in the main parser for the configuration file
-	  extensions.conf, the separator was changed from vertical bar to comma.
-	  However, the first separator was not changed in aelparse; it still had
-	  to be a vertical bar, and no comma was allowed.
+	* app_dial: Flip stream direction of outgoing channel.
 
-	  Additionally, this change allows the vertical bar for the first and
-	  last parameter again, even in the main parser, because the vertical bar
-	  was still accepted for the other parameters.
+	  When executing dial, the topology of the incoming channel is cloned and
+	  used for the outgoing channel. This creates issues when an incoming
+	  stream is sendonly or recvonly as the stream state of the outgoing
+	  channel will be the same as the stream state of the incoming channel.
 
-	  ASTERISK-29540
+	  Now the stream state is flipped for the outgoing stream in
+	  dial_exec_full if the incoming stream topology is recvonly or sendonly.
 
-	  Change-Id: I882e17c73adf4bf2f20f9046390860d04a9f8d81
+	  ASTERISK-29655
+	  Reported by: Michael Auracher
 
-2021-08-03 11:30 +0000 [628830921e]  Kevin Harwell <kharwell@sangoma.com>
+	  ASTERISK-29638
+	  Reported by: Michael Auracher
 
-	* format_ogg_speex: Implement a "not supported" write handler
+	  Change-Id: I294dc834ac9a5f048b101b691669959e9df630e1
 
-	  This format did not specify a "write" handler, so when attempting to write
-	  to it (ast_writestream) a crash would occur.
+2022-04-21 10:26 +0000 [f593b1e93b]  Ben Ford <bford@digium.com>
 
-	  This patch adds a default handler that simply issues a "not supported"
-	  warning, thus no longer crashing.
+	* res_pjsip_stir_shaken.c: Fix enabled when not configured.
 
-	  ASTERISK-29539
+	  There was an issue with the conditional where STIR/SHAKEN would be
+	  enabled even when not configured. It has been changed to ensure that if
+	  a profile does not exist and stir_shaken is not set in pjsip.conf, then
+	  the conditional will return from the function without performing
+	  STIR/SHAKEN operations.
 
-	  Change-Id: I8f6ddc7cc3b15da30803be3b1cf68e2ba0fbce91
+	  ASTERISK-30024
 
-2021-08-05 14:28 +0000  Asterisk Development Team <asteriskteam@digium.com>
+	  Change-Id: I41286a3d35b033ccbfbe4129427a62cb793a86e6
 
-	* asterisk 18.6.0-rc1 Released.
+2022-04-06 05:23 +0000 [fdc1c750f3]  Joshua C. Colp <jcolp@sangoma.com>
 
-2021-06-28 08:48 +0000 [adf707f2ae]  Naveen Albert <asterisk@phreaknet.org>
+	* res_pjsip: Always set async_operations to 1.
 
-	* cdr_adaptive_odbc: Prevent filter warnings
+	  The async_operations setting on a transport configures how
+	  many simultaneous incoming packets the transport can handle
+	  when multiple threads are polling and waiting on the transport.
+	  As we only use a single thread this was needlessly creating
+	  incoming packets when set to a non-default value, wasting memory.
 
-	  Previously, if CDR filters were used so that
-	  not all CDR records used all sections defined
-	  in cdr_adaptive_odbc.conf, then warnings will
-	  always be emitted (if each CDR record is unique
-	  to a particular section, n-1 warnings to be
-	  specific).
+	  ASTERISK-30006
 
-	  This turns the offending warning log into
-	  a verbose message like the other one, since
-	  this behavior is intentional and not
-	  indicative of anything wrong.
+	  Change-Id: I1915973ef352862dc2852a6ba4cfce2ed536e68f
 
-	  ASTERISK-29494
+2022-04-19 10:36 +0000 [b1e0527bbd]  Sean Bright <sean.bright@gmail.com>
 
-	  Change-Id: Ifd314fa9298722bc99494d5ca2658a5caa94a5f8
+	* config.h: Don't use C++ keywords as argument names.
 
-2021-07-25 16:53 +0000 [940f6c4a03]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-30021 #close
 
-	* app_queue: Allow streaming multiple announcement files
+	  Change-Id: I70eb59b782a4946b979942e21422746b7563029c
 
-	  Allows multiple files comprising an agent announcement
-	  to be played by separating on the ampersand, similar
-	  to the multi-file support in other Asterisk applications.
+2022-04-20 07:40 +0000 [283b09cf70]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  ASTERISK-29528
+	* cdr_adaptive_odbc: Add support for SQL_DATETIME field type.
 
-	  Change-Id: Iec600d8cd5ba14aa1e4e37f906accb356cd7891a
+	  ASTERISK-30023
 
-2021-04-13 02:36 +0000 [1e4ed61a2b]  Igor Goncharovsky <igorg@iqtek.ru>
+	  Change-Id: I0e1697f6af044e9eab7e07bbaeeffd1bb68ac34a
 
-	* res_pjsip_header_funcs: Add PJSIP_HEADERS() ability to read header by pattern
+2022-04-11 04:30 +0000 [b3f39be0cc]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  PJSIP currently does not provide a function to replace SIP_HEADERS() function to get a list of headers from INVITE request.
-	  It may be used to get all X- headers in case the actual set and names of headers unknown.
+	* pjsip: Increase maximum number of format attributes.
 
-	  ASTERISK-29389
+	  Chrome has added more attributes, causing the limit to be
+	  exceeded. This raises it up some more.
 
-	  Change-Id: Ic09d395de71a0021e0d6c5c29e1e19d689079f8b
+	  ASTERISK-30015
 
-2021-07-08 07:34 +0000 [71dd1d91ad]  Rijnhard Hessel <rijnhard@teleforge.co.za>
+	  Change-Id: I964957c005c4e6f7871b15ea1ccd9b4659c7ef32
 
-	* res_statsd: handle non-standard meter type safely
+2022-02-28 11:19 +0000 [0724b767a3]  Ben Ford <bford@digium.com>
 
-	  Meter types are not well supported,
-	  lacking support in telegraf, datadog and the official statsd servers.
-	  We deprecate meters and provide a compliant fallback for any existing usages.
+	* AST-2022-002 - res_stir_shaken/curl: Add ACL checks for Identity header.
 
-	  A flag has been introduced to allow meters to fallback to counters.
+	  Adds a new configuration option, stir_shaken_profile, in pjsip.conf that
+	  can be specified on a per endpoint basis. This option will reference a
+	  stir_shaken_profile that can be configured in stir_shaken.conf. The type
+	  of this option must be 'profile'. The stir_shaken option can be
+	  specified on this object with the same values as before (attest, verify,
+	  on), but it cannot be off since having the profile itself implies wanting
+	  STIR/SHAKEN support. You can also specify an ACL from acl.conf (along
+	  with permit and deny lines in the object itself) that will be used to
+	  limit what interfaces Asterisk will attempt to retrieve information from
+	  when reading the Identity header.
 
+	  ASTERISK-29476
 
-	  ASTERISK-29513
+	  Change-Id: I87fa61f78a9ea0cd42530691a30da3c781842406
 
-	  Change-Id: I5fcb385983a1b88f03696ff30a26b55c546a1dd7
+2022-01-07 08:50 +0000 [8f3dd86b8d]  Ben Ford <bford@digium.com>
 
-2021-07-22 11:39 +0000 [feb1e06ac5]  under <pcapdump@gmail.com>
+	* AST-2022-001 - res_stir_shaken/curl: Limit file size and check start.
 
-	* codec_builtin.c: G729 audio gets corrupted by Asterisk due to smoother
+	  Put checks in place to limit how much we will actually download, as well
+	  as a check for the data we receive at the start to ensure it begins with
+	  what we would expect a certificate to begin with.
 
-	  If Asterisk gets G.729 6-byte VAD frames inbound, then at outbound Asterisk sends this G.729 stream with non-continuous timestamps.
-	  This makes the audio stream not-playable at the receiver side.
-	  Linphone isn't able to play such an audio - lots of disruptions are heard.
-	  Also I had complains of bad audio from users which use other types of phones.
+	  ASTERISK-29872
 
-	  After debugging, I found this is a regression connected with RTP Smoother (main/smoother.c).
+	  Change-Id: Ifd3c6b8bd52b8b6192a04166ccce4fc8a8000b46
 
-	  Smoother has a special code to handle G.729 VAD frames (search for AST_SMOOTHER_FLAG_G729 in smoother.c).
+2022-02-10 06:02 +0000 [4aedaaadeb]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  However, this flag is never set in Asterisk-12 and newer.
-	  Previously it has been set (see Asterisk-11).
+	* func_odbc: Add SQL_ESC_BACKSLASHES dialplan function.
 
-	  ASTERISK-29526 #close
+	  Some databases depending on their configuration using backslashes
+	  for escaping. When combined with the use of ' this can result in
+	  a broken func_odbc query.
 
-	  Change-Id: I6f51ecb1a3ecd9c6d59ec5a6811a27446e17065d
+	  This change adds a SQL_ESC_BACKSLASHES dialplan function which can
+	  be used to escape the backslashes.
 
-2021-06-16 15:26 +0000 [016f6a0e14]  Naveen Albert <asterisk@phreaknet.org>
+	  This is done as a dialplan function instead of being always done
+	  as some databases do not require this, and always doing it would
+	  result in incorrect data being put into the database.
 
-	* app_dtmfstore: New application to store digits
+	  ASTERISK-29838
 
-	  Adds application to asynchronously collect digits
-	  dialed on a channel in the TX or RX direction
-	  using a framehook and stores them in a specified
-	  variable, up to a configurable number of digits.
+	  Change-Id: I152bf34899b96ddb09cca3e767254d8d78f0c83d
 
-	  ASTERISK-29477
+2022-03-04 19:41 +0000 [b87c5f5124]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I51aa93fc9507f7636ac44806c4420ce690423e6f
+	* app_mf, app_sf: Return -1 if channel hangs up.
 
-2021-07-27 07:53 +0000 [9117f09d28]  Joshua C. Colp <jcolp@sangoma.com>
+	  The ReceiveMF and ReceiveSF applications currently always
+	  return 0, even if a channel has hung up. The call will still
+	  end but generally applications are expected to return -1 if
+	  the channel has hung up.
 
-	* docs: Remove embedded macro in WaitForCond XML documentation.
+	  We now return -1 if a hangup occured to bring this behavior
+	  in line with this norm. This has no functional impact, but
+	  merely increases conformity with how these modules interact
+	  with the PBX core.
 
-	  Change-Id: I40c6514e1843e320f3cbe0b2c70d4a98c0e35b9c
+	  ASTERISK-29951 #close
 
-2021-07-22 16:56 +0000 [993b3ba919]  Asterisk Development Team <asteriskteam@digium.com>
+	  Change-Id: I234d755050ab8ed58f197c6925b968ba26b14033
 
-	* Update CHANGES and UPGRADE.txt for 18.5.1
-2021-06-14 13:28 +0000 [3025ef4f6e]  Kevin Harwell <kharwell@sangoma.com>
+2022-01-22 09:53 +0000 [ede4e2099f]  Naveen Albert <asterisk@phreaknet.org>
 
-	* AST-2021-009 - pjproject-bundled: Avoid crash during handshake for TLS
+	* app_queue: Add music on hold option to Queue.
 
-	  If an SSL socket parent/listener was destroyed during the handshake,
-	  depending on timing, it was possible for the handling callback to
-	  attempt access of it after the fact thus causing a crash.
+	  Adds the m option to the Queue application, which allows a
+	  music on hold class to be specified at runtime which will
+	  override the class configured in queues.conf.
 
-	  ASTERISK-29415 #close
+	  This option functions like the m option to Dial.
 
-	  Change-Id: I105dacdcd130ea7fdd4cf2010ccf35b5eaf1432d
+	  ASTERISK-29876 #close
 
-2021-05-10 17:59 +0000 [2a141a58b6]  Kevin Harwell <kharwell@sangoma.com>
+	  Change-Id: Ie25a48569cf8755c305c9438b1ed292c3adcf8d7
 
-	* AST-2021-008 - chan_iax2: remote crash on unsupported media format
+2022-03-05 09:40 +0000 [da44b848f5]  Naveen Albert <asterisk@phreaknet.org>
 
-	  If chan_iax2 received a packet with an unsupported media format, for
-	  example vp9, then it would set the frame's format to NULL. This could
-	  then result in a crash later when an attempt was made to access the
-	  format.
+	* app_meetme: Emit warning if conference not found.
 
-	  This patch makes it so chan_iax2 now ignores/drops frames received
-	  with unsupported media format types.
+	  Currently, if a user tries to access a non-dynamic
+	  MeetMe conference and the conference is not found,
+	  the call simply silent hangs up. There is no indication
+	  to the user that anything went wrong at all.
 
-	  ASTERISK-29392 #close
+	  This changes the relevant debug message to a warning
+	  so that the user is notified of this invalidity.
 
-	  Change-Id: Ifa869a90dafe33eed8fd9463574fe6f1c0ad3eb1
+	  ASTERISK-29954 #close
 
-2021-04-28 07:36 +0000 [523a795289]  Joshua C. Colp <jcolp@sangoma.com>
+	  Change-Id: Iebcfae3755d00f2150d676ee211c57bc59530048
 
-	* AST-2021-007 - res_pjsip_session: Don't offer if no channel exists.
+2022-02-24 13:33 +0000 [94df607771]  Naveen Albert <asterisk@phreaknet.org>
 
-	  If a re-INVITE is received after we have sent a BYE request then it
-	  is possible for no channel to be present on the session. If this
-	  occurs we allow PJSIP to produce the offer instead. Since the call
-	  is being hung up if it produces an incorrect offer it doesn't
-	  actually matter. This also ensures that code which produces SDP
-	  does not need to handle if a channel is not present.
+	* build: Remove obsolete leftover build references.
 
-	  ASTERISK-29381
+	  Removes some leftover build and config references to
+	  modules that have since been removed from Asterisk.
 
-	  Change-Id: I673cb88c432f38f69b2e0851d55cc57a62236042
+	  ASTERISK-29935 #close
 
-2021-06-29 11:07 +0000 [2c3defc6c6]  Andre Barbosa <andre.emanuel.barbosa@gmail.com>
+	  Change-Id: Iaefc73a23f4b2de3c6c14d928050135b6d0ef6af
 
-	* res_stasis_playback: Check for chan hangup on play_on_channels
+2022-03-23 17:45 +0000 [0e31df6c93]  Kevin Harwell <kharwell@sangoma.com>
 
-	  Verify `ast_check_hangup` before looping to the next sound file.
-	  If the call is already hangup we just break the cycle.
-	  It also ensures that the PlaybackFinished event is sent if the call was hangup.
+	* res_pjsip_header_funcs: wrong pool used tdata headers
 
-	  This is also use-full when we are playing a big list of file for a channel that is hangup.
-	  Before this patch Asterisk will give a warning for every sound not played and fire a PlaybackStart for every sound file on the list tried to be played.
+	  When adding headers to an outgoing request the headers were cloned using
+	  the dialog's pool when they should have been cloned using tdata's pool.
+	  Under certain circumstances it was possible for the dialog object, and
+	  its pool to be freed while tdata is still active and available. Thus the
+	  cloned header "disappeared", and when tdata tried to later access it a
+	  crash would occur.
 
-	  With the patch we just break the playback cycle when the chan is hangup.
+	  This patch makes it so all added headers are cloned appropriately using
+	  tdata's pool.
 
-	  ASTERISK-29501 #close
+	  ASTERISK-29411 #close
+	  ASTERISK-29535 #close
 
-	  Change-Id: Ic4e1c01b974c9a1f2d9678c9d6b380bcfc69feb8
+	  Change-Id: I9852025b5ee93ce1c038209150ee9dba1e0767c5
 
-2021-07-15 15:04 +0000 [30feaadabf]  Sean Bright <sean.bright@gmail.com>
+2022-03-25 10:46 +0000 [30cefc97a6]  Kevin Harwell <kharwell@sangoma.com>
 
-	* res_pjsip_stir_shaken: RFC 8225 compliance and error message cleanup.
+	* deprecation cleanup: remove leftover files
 
-	  From RFC 8225 Section 5.2.1:
+	  Several modules removal and deprecations occurred in 19.0.0 (initial
+	  19 release), but associated UPGRADE files were not removed from
+	  staging for some reason in the master branch.
 
-	      The "dest" claim is a JSON object with the claim name of "dest"
-	      and MUST have at least one identity claim object.  The "dest"
-	      claim value is an array containing one or more identity claim JSON
-	      objects representing the destination identities of any type
-	      (currently "tn" or "uri").  If the "dest" claim value array
-	      contains both "tn" and "uri" claim names, the JSON object should
-	      list the "tn" array first and the "uri" array second.  Within the
-	      "tn" and "uri" arrays, the identity strings should be put in
-	      lexicographical order, including the scheme-specific portion of
-	      the URI characters.
+	  This patch removes those files, and also removes a spurious leftover
+	  header, chan_phone.h (associated module removed in 19).
 
-	  Additionally, make it clear that there was a failure to sign the JWT
-	  payload and not necessarily a memory allocation failure.
+	  Change-Id: Ib92142c846b45c882d6b2b6caca7225253c83add
 
-	  Change-Id: Ia8733b861aef6edfaa9c2136e97b447a01578dc9
+2022-02-24 11:48 +0000 [fa0078fbe4]  Joshua C. Colp <jcolp@sangoma.com>
 
-2021-06-30 17:15 +0000 [4bd975f415]  Sebastien Duthil <sduthil@wazo.community>
+	* pjproject: Update bundled to 2.12 release.
 
-	* stun: Emit warning message when STUN request times out
+	  This change removes patches which have been merged into
+	  upstream and updates some existing ones. It also adds
+	  some additional config_site.h changes to restore previous
+	  behavior, as well as a patch to allow multiple Authorization
+	  headers. There seems to be some confusion or disagreement
+	  on language in RFC 8760 in regards to whether multiple
+	  Authorization headers are supported. The RFC implies it
+	  is allowed, as does some past sipcore discussion. There is
+	  also the catch all of "local policy" to allow it. In
+	  the case of Asterisk we allow it.
 
-	  Without this message, it is not obvious that the reason is STUN timeout.
+	  ASTERISK-29351
 
-	  ASTERISK-29507 #close
+	  Change-Id: Id39ece02dedb7b9f739e0e37ea47d76854af7191
 
-	  Change-Id: I26e4853c23a1aed324552e1b9683ea3c05cb1f74
+2022-03-05 10:26 +0000 [a7cf3979ec]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-07-02 10:15 +0000 [76c09b1cfd]  Sean Bright <sean.bright@gmail.com>
+	* pbx.c: Warn if there are too many includes in a context.
 
-	* res_http_media_cache.c: Parse media URLs to find extensions.
+	  The PBX core uses the stack when it comes to includes, which
+	  means that a context can only contain strictly fewer than
+	  AST_PBX_MAX_STACK includes. If this is exceeded, then warnings
+	  will be emitted for each number of includes beyond this if
+	  searching for an extension in the including context, and if
+	  the extension's inclusion is beyond the stack size, it will
+	  simply not be found.
 
-	  Use the URI parsing functions to parse playback URLs in order to find
-	  their file extensions.
+	  To address this, we now check if there are too many includes
+	  in a context when the dialplan is reloaded so that if there
+	  is an issue, the user is aware of at "compile time" as opposed
+	  to "run time" only. Secondly, more details are printed out
+	  when this message is encountered so it's clear what has happened.
 
-	  For backwards compatibility, we first look at the full URL, then at
-	  any Content-Type header, and finally at just the path portion of the
-	  URL.
+	  ASTERISK-26719
 
-	  ASTERISK-27871 #close
+	  Change-Id: Ia3700452e75a7af3391b3e82ee69f06a669f8958
 
-	  Change-Id: I16d0682f6d794be96539261b3e48f237909139cb
+2022-03-25 14:00 +0000 [3e97156fd3]  George Joseph <gjoseph@digium.com>
 
-2021-07-13 10:31 +0000 [fcebc4d24a]  Sean Bright <sean.bright@gmail.com>
+	* Makefile:  Disable XML doc validation
 
-	* main/cdr.c: Correct Party A selection.
+	  make_xml_documentation was being called with the --validate
+	  flag set when it shouldn't have been.  This was causing
+	  build failures if neither xmllint nor xmlstarlet were installed.
+	  The correct behavior is to simply print a message that either
+	  one of those tools should be installed for validation and
+	  continue with the build.
 
-	  This appears to just have been a copy/paste error from 6258bbe7. Fix
-	  suggested by Ross Beer in ASTERISK~29166.
+	  ASTERISK-29988
 
-	  Change-Id: I51e0de92042e53f37597c6f83a75621ef0d1ae37
+	  Change-Id: Idc6c44114e7dd3fadae183a4e22f4fdba0b8a645
 
-2021-05-26 12:09 +0000 [a41d192e99]  Naveen Albert <asterisk@phreaknet.org>
+2022-03-25 09:33 +0000 [144b3c5453]  George Joseph <gjoseph@digium.com>
 
-	* app_reload: New Reload application
+	* make_xml_documentation: Remove usage of get_sourceable_makeopts
 
-	  Adds an application to reload modules
-	  from within the dialplan.
+	  get_sourceable_makeopts wasn't handling variables with embedded
+	  double quotes in them very well.  One example was the DOWNLOAD
+	  variable when curl was being used instead of wget.  Rather than
+	  trying to fix get_sourceable_makeopts, it's just been removed.
 
-	  ASTERISK-29454
+	  ASTERISK-29986
+	  Reported by: Stefan Ruijsenaars
 
-	  Change-Id: Ic8ab025d8b38dd525b872b41c465c999c5810774
+	  Change-Id: Idf2a90902228c2558daa5be7a4f8327556099cd2
 
-2021-07-08 09:32 +0000 [b9bb96ffed]  Igor Goncharovsky <igorg@iqtek.ru>
+2022-02-04 18:36 +0000 [0d11938e92]  Birger Harzenetter (license 5870)
 
-	* res_ari: Fix audiosocket segfault
+	* chan_iax2: Fix spacing in netstats command
 
-	  Add check that data parameter specified when audiosocket used for externalMedia.
+	  The iax2 show netstats command previously didn't contain
+	  enough spacing in the header to properly align the table
+	  header with the table body. This caused column headers
+	  to not align with the values on longer channel names.
 
-	  ASTERISK-29514 #close
+	  Some spacing is added to account for the longest channel
+	  names that display (before truncation occurs) so that
+	  columns are always properly aligned.
 
-	  Change-Id: Ie562f03c5d6c3835a3631f376b3d43e75b8f9617
+	  ASTERISK-29895 #close
+	  patches:
+	    61205_misaligned2.patch submitted by Birger Harzenetter (license 5870)
 
-2021-06-30 08:07 +0000 [146b59df3f]  Sean Bright <sean.bright@gmail.com>
+	  Change-Id: I450ce6bb81157b9d6d149007e53b749f237b6d9f
 
-	* res_pjsip_config_wizard.c: Add port matching support.
+2022-03-25 08:19 +0000 [5ac5c2b0ab]  Sean Bright <sean.bright@gmail.com>
 
-	  In f8b0c2c9 we added support for port numbers in 'match' statements
-	  but neglected to include that support in the PJSIP config wizard.
+	* openssl: Supress deprecation warnings from OpenSSL 3.0
 
-	  The removed code would have also prevented IPv6 addresses from being
-	  successfully used in the config wizard as well.
+	  There is work going on to update our OpenSSL usage to avoid the
+	  deprecated functions but in the meantime make it possible to compile
+	  in devmode.
 
-	  ASTERISK-29503 #close
+	  Change-Id: Ib082eb8b3751f0185d8aa8fe127da664c93f0726
 
-	  Change-Id: Idd5bbfd48009e7a741757743dbaea68e2835a34d
+2022-03-23 16:04 +0000 [9b654d4e98]  Marcel Wagner <mwagner@sipgate.de>
 
-2021-05-22 09:31 +0000 [1b21b1abf7]  Naveen Albert <mail@interlinked.x10host.com>
+	* documentation: Add information on running install_prereq script in readme
 
-	* app_waitforcond: New application
+	  Adding information in the readme about running the install_preqreq script to install components that the ./configure script might indicate as missing.
 
-	  While several applications exist to wait for
-	  a certain event to occur, none allow waiting
-	  for any generic expression to become true.
-	  This application allows for waiting for a condition
-	  to become true, with configurable timeout and
-	  checking interval.
+	  ASTERISK-29976 #close
 
-	  ASTERISK-29444
+	  Change-Id: Ic287b46300168729838bddd8f9265e98fc22bce6
 
-	  Change-Id: I08adf2824b8bc63405778cf355963b5005612f41
+2022-03-13 12:46 +0000 [7bc8ef2681]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-06-04 06:11 +0000 [283812e492]  Andre Barbosa <andre.emanuel.barbosa@gmail.com>
+	* chan_iax2: Fix perceived showing host address.
 
-	* res_stasis_playback: Send PlaybackFinish event only once for errors
+	  ASTERISK_22025 introduced a regression that shows
+	  the host IP and port as the perceived IP and port
+	  again, as opposed to showing the actual perceived
+	  address. This fixes this by showing the correct
+	  information.
 
-	  When we try to play a list of sound files in the same Play command,
-	  we get only one PlaybackFinish event, after all sounds are played.
+	  ASTERISK-29048 #close
 
-	  But in the case where the Play fails (because channel is destroyed
-	  for example), Asterisk will send one PlaybackFinish event for each
-	  sound file still to be played. If the list is big, Asterisk is
-	  sending many events.
+	  Change-Id: I0ad3e25bc6b449e83ce72ea5d1a1cdba72aa304a
 
-	  This patch adds a failed state so we can understand that the play
-	  failed. On that case we don't send the event, if we still have a
-	  list of sounds to be played.
+2022-02-22 14:51 +0000 [6624e34580]  Boris P. Korzun <drtr0jan@yandex.ru>
 
-	  When we reach the last sound, we send the PlaybackFinish with
-	  the failed state.
+	* res_pjsip_sdp_rtp: Improve detecting of lack of RTP activity
 
-	  ASTERISK-29464 #close
+	  Change RTP timer behavior for detecting RTP only after two-way
+	  SDP channel establishment. Ignore detecting after receiving 183
+	  with SDP or while direct media is used.
+	  Make rtp_timeout and rtp_timeout_hold options consistent to rtptimeout
+	  and rtpholdtimeout options in chan_sip.
 
-	  Change-Id: I4c2e5921cc597702513af0d7c6c2c982e1798322
+	  ASTERISK-26689 #close
+	  ASTERISK-29929 #close
 
-2021-06-17 07:57 +0000 [88da59efe7]  George Joseph <gjoseph@digium.com>
+	  Change-Id: I07326d5b9c40f25db717fd6075f6f3a8d77279eb
 
-	* jitterbuffer:  Correct signed/unsigned mismatch causing assert
+2022-03-15 19:06 +0000 [64f11e0d18]  Hugh McMaster <hugh.mcmaster@outlook.com>
 
-	  If the system time has stepped backwards because of a time
-	  adjustment between the time a frame is timestamped and the
-	  time we check the timestamps in abstract_jb:hook_event_cb(),
-	  we get a negative interval, but we don't check for that there.
-	  abstract_jb:hook_event_cb() then calls
-	  fixedjitterbuffer:fixed_jb_get() (via abstract_jb:jb_get_fixed)
-	  and the first thing that does is assert(interval >= 0).
+	* configure.ac: Use pkg-config to detect libxml2
 
-	  There are several issues with this...
+	  Use pkg-config to detect libxml2, falling back to xml2-config if the
+	  former is not available.
 
-	   * abstract_jb:hook_event_cb() saves the interval in a variable
-	     named "now" which is confusing in itself.
+	  This patch ensures Asterisk continues to build on systems without
+	  xml2-config installed.
 
-	   * "now" is defined as an unsigned int which converts the negative
-	     value returned from ast_tvdiff_ms() to a large positive value.
+	  The patch also updates the associated 'configure' files.
 
-	   * fixed_jb_get()'s parameter is defined as a signed int so the
-	     interval gets converted back to a negative value.
+	  ASTERISK-29970 #close
 
-	   * fixed_jb_get()'s assert is NOT an ast_assert but a direct define
-	     that points to the system assert() so it triggers even in
-	     production mode.
+	  Change-Id: I3c90dfe0b0590486cbb8e6d426a7c5c4199410c0
 
-	  So...
+2022-02-13 13:06 +0000 [287a1a9126]  Philip Prindeville <philipp@redfish-solutions.com>
 
-	   * hook_event_cb()'s "now" was renamed to "relative_frame_start" and
-	     changed to an int64_t.
-	   * hook_event_cb() now checks for a negative value right after
-	     retrieving both the current and framedata timestamps and just
-	     returns the frame if the difference is negative.
-	   * fixed_jb_get()'s local define of ASSERT() was changed to call
-	     ast_assert() instead of the system assert().
+	* time: add support for time64 libcs
 
-	  ASTERISK-29480
-	  Reported by: Dan Cropp
+	  Treat time_t's as entirely unique and use the POSIX API's for
+	  converting to/from strings.
 
-	  Change-Id: Ic469dec73c2edc3ba134cda6721a999a9714f3c9
+	  Lastly, a 64-bit integer formats as 20 digits at most in base10.
+	  Don't need to have any 100 byte buffers to hold that.
 
-2021-05-21 19:08 +0000 [c4236dcff2]  Naveen Albert <mail@interlinked.x10host.com>
+	  ASTERISK-29674 #close
 
-	* app_dial: Expanded A option to add caller announcement
+	  Signed-off-by: Philip Prindeville <philipp@redfish-solutions.com>
+	  Change-Id: Id7b25bdca8f92e34229f6454f6c3e500f2cd6f56
 
-	  Hitherto, the A option has made it possible to play
-	  audio upon answer to the called party only. This option
-	  is expanded to allow for playback of an audio file to
-	  the caller instead of or in addition to the audio
-	  played to the answerer.
+2022-03-15 12:24 +0000 [d1900d4a4c]  Alexei Gradinari <alex2grad@gmail.com>
 
-	  ASTERISK-29442
+	* res_pjsip_pubsub: RLS 'uri' list attribute mismatch with SUBSCRIBE request
 
-	  Change-Id: If6eed3ff5c341dc8c588c8210987f2571e891e5e
+	  When asterisk generates the RLMI part of NOTIFY request,
+	  the asterisk uses the local contact uri instead of the URI to which
+	  the SUBSCRIBE request is sent.
+	  Because of this mismatch some IP phones (for example Cisco 5XX) ignore
+	  this list.
 
-2021-06-21 06:31 +0000 [5e1cb3253c]  Joshua C. Colp <jcolp@sangoma.com>
+	  According
+	  https://datatracker.ietf.org/doc/html/rfc4662#section-5.2
+	    The first mandatory <list> attribute is "uri", which contains the uri
+	    that corresponds to the list. Typically, this is the URI to which
+	    the SUBSCRIBE request was sent.
+	  https://datatracker.ietf.org/doc/html/rfc4662#section-5.3
+	    The "uri" attribute identifies the resource to which the <resource>
+	    element corresponds. Typically, this will be a SIP URI that, if
+	    subscribed to, would return the state of the resource.
 
-	* core: Don't play silence for Busy() and Congestion() applications.
+	  This patch makes asterisk to generate URI using SUBSCRIBE request URI.
 
-	  When using the Busy() and Congestion() applications the
-	  function ast_safe_sleep is used by wait_for_hangup to safely
-	  wait on the channel. This function may send silence if Asterisk
-	  is configured to do so using the transmit_silence option.
+	  ASTERISK-29961 #close
 
-	  In a scenario where an answered channel dials a Local channel
-	  either directly or through call forwarding and the Busy()
-	  or Congestion() dialplan applications were executed with the
-	  transmit_silence option enabled the busy or congestion
-	  tone would not be heard.
+	  Change-Id: I1fcfc08fd589677f40608c59a4e143c45ee05f6c
 
-	  This is because inband generation of tones (such as busy
-	  and congestion) is stopped when other audio is sent to
-	  the channel they are being played to. In the given
-	  scenario the transmit_silence option would result in
-	  silence being sent to the channel, thus stopping the
-	  inband generation.
+2022-03-05 06:04 +0000 [1e87cadf8e]  Naveen Albert <asterisk@phreaknet.org>
 
-	  This change adds a variant of ast_safe_sleep which can be
-	  used when silence should not be played to the channel. The
-	  wait_for_hangup function has been updated to use this
-	  resulting in the tones being generated as expected.
+	* app_dial: Document DIALSTATUS return values.
 
-	  ASTERISK-29485
+	  Adds documentation for all of the possible return values
+	  for the DIALSTATUS variable in the Dial application.
 
-	  Change-Id: I066bfc987a3ad6f0ccc88e0af4cd63f6a4729133
+	  ASTERISK-25716
 
-2021-05-07 01:18 +0000 [6b041d1092]  Bernd Zobl <b.zobl@commend.com>
+	  Change-Id: Id22593f1f1f7ea86e5734cee49516ec50848e8c0
 
-	* res_pjsip_sdp_rtp: Evaluate remotely held for Session Progress
+2022-03-10 11:07 +0000 [d3abdf0b8d]  Sean Bright <sean.bright@gmail.com>
 
-	  With the fix for ASTERISK_28754 channels are no longer put on hold if an
-	  outbound INVITE is answered with a "Session Progress" containing
-	  "inactive" audio.
+	* stasis_recording: Perform a complete match on requested filename.
 
-	  The previous change moved the evaluation of the media attributes to
-	  `negotiate_incoming_sdp_stream()` to have the `remotely_held` status
-	  available when building the SDP in `create_outgoing_sdp_stream()`.
-	  This however means that an answer to an outbound INVITE, which does not
-	  traverse `negotiate_incoming_sdp_stream()`, cannot set the
-	  `remotely_held` status anymore.
+	  Using the length of a file found on the filesystem rather than the
+	  file being requested could result in filenames whose names are
+	  substrings of another to be erroneously matched.
 
-	  This change moves the check so that both, `negotiate_incoming_sdp_stream()` and
-	  `apply_negotiated_sdp_stream()` can do the checks.
+	  We now ensure a complete comparison before returning a positive
+	  result.
 
-	  ASTERISK-29479
+	  ASTERISK-29960 #close
 
-	  Change-Id: Icde805a819399d5123b688e1ed1d2bcd9d5b0f75
+	  Change-Id: Id3ffc77681b9b75b8569062f3d952a128a21c71a
 
-2021-06-17 14:44 +0000  Asterisk Development Team <asteriskteam@digium.com>
+2022-03-22 09:01 +0000 [686c386b05]  Sean Bright <sean.bright@gmail.com>
 
-	* asterisk 18.5.0-rc1 Released.
+	* download_externals: Use HTTPS for downloads
 
-2021-06-17 09:39 +0000 [0747162d4f]  Asterisk Development Team <asteriskteam@digium.com>
+	  ASTERISK-29980 #close
 
-	* Update CHANGES and UPGRADE.txt for 18.5.0
-2021-06-16 08:50 +0000 [702e1d33b5]  George Joseph <gjoseph@digium.com>
+	  Change-Id: I7b347665822ea2774dd322276c09be67914d2065
 
-	* res_pjsip_messaging: Overwrite user in existing contact URI
+2022-03-04 14:26 +0000 [c33718a54d]  Sean Bright <sean.bright@gmail.com>
 
-	  When the MessageSend destination is in the form
-	  PJSIP/<number>@<endpoint> and the endpoint's contact
-	  URI already has a user component, that user component
-	  will now be replaced with <number> when creating the
-	  request URI.
+	* conversions.c: Specify that we only want to parse decimal numbers.
 
-	  ASTERISK_29404
+	  Passing 0 as the last argument to strtoimax() or strtoumax() causes
+	  octal and hexadecimal to be accepted which was not originally
+	  intended. So we now force to only accept decimal.
 
-	  Change-Id: I80e5910fa25c803d1440da0594a0d6b34b6b4ad5
+	  ASTERISK-29950 #close
 
-2021-03-16 11:45 +0000 [804788037e]  Bernd Zobl <b.zobl@commend.com>
+	  Change-Id: I93baf0f273441e8280354630a463df263a8c0edd
 
-	* res_pjsip/pjsip_message_filter: set preferred transport in pjsip_message_filter
+2022-02-21 19:05 +0000 [2a87303ebd]  Philip Prindeville <philipp@redfish-solutions.com>
 
-	  Set preferred transport when querying the local address to use in
-	  filter_on_tx_messages(). This prevents the module to erroneously select
-	  the wrong transport if more than one transports of the same type (TCP or
-	  TLS) are configured.
+	* logger: workaround woefully small BUFSIZ in MUSL
 
-	  ASTERISK-29241
+	  MUSL defines BUFSIZ as 1024 which is not reasonable for log messages.
 
-	  Change-Id: I598e60257a7f92b29efce1fb3e9a2fc06f1439b6
+	  More broadly, BUFSIZ is the amount of buffering stdio.h does, which
+	  is arbitrary and largely orthogonal to what logging should accept
+	  as the maximum message size.
 
-2021-06-10 09:34 +0000 [2b174a38fe]  Naveen Albert <asterisk@phreaknet.org>
+	  ASTERISK-29928
 
-	* pbx_builtins: Corrects SayNumber warning
+	  Signed-off-by: Philip Prindeville <philipp@redfish-solutions.com>
+	  Change-Id: Iaa49fbbab029c64ae3d95e4b18270e0442cce170
 
-	  Previously, SayNumber always emitted a warning if the caller hung up
-	  during execution. Usually this isn't correct, so check if the channel
-	  hung up and, if so, don't emit a warning.
+2022-03-14 11:57 +0000 [fd29d28832]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29475
+	* pbx_builtins: Add missing options documentation
 
-	  Change-Id: Ieea4a67301c6ea83bbc7690c1d4808d79a704594
+	  BackGround and WaitExten both accept options that are not
+	  currently documented. This adds documentation for these
+	  options to the xml documentation for each application.
 
-2021-05-22 07:53 +0000 [6b67821098]  Jaco Kroon <jaco@uls.co.za>
+	  ASTERISK-29967 #close
 
-	* func_lock: Prevent module unloading in-use module.
+	  Change-Id: If812a9f1ccbba3e4d427a0e7a6dea923c2f905f7
 
-	  The scenario where a channel still has an associated datastore we
-	  cannot unload since there is a function pointer to the destroy and fixup
-	  functions in play.  Thus increase the module ref count whenever we
-	  allocate a datastore, and decrease it during destroy.
+2022-02-08 16:58 +0000 [edce853123]  Alexei Gradinari <alex2grad@gmail.com>
 
-	  In order to tighten the race that still exists in spite of this (below)
-	  add some extra failure cases to prevent allocations in these cases.
+	* res_pjsip_pubsub: update RLS to reflect the changes to the lists
 
-	  Race:
+	  This patch makes the Resource List Subscriptions (RLS) dynamic.
+	  The asterisk updates the current subscriptions to reflect the changes
+	  to the list on the subscriptions refresh. If list items are added,
+	  removed, updated or do not exist anymore, the asterisk regenerates
+	  the resource list.
 
-	  If module ref is zero, an LOCK or TRYLOCK is invoked (near)
-	  simultaneously on a channel that has NOT PREVIOUSLY taken a lock, and if
-	  in such a case the datastore is created *prior* to unloading being set
-	  to true (first step in module unload) then it's possible that the module
-	  will unload with the destructor being called (and segfault) post the
-	  module being unloaded.  The module will however wait for such locks to
-	  release prior to unloading.
+	  ASTERISK-29906 #close
 
-	  If post that we can recheck the module ref before returning the we can
-	  (in theory, I think) eliminate the last of the race.  This race is
-	  mostly theoretical in nature.
+	  Change-Id: Icee8c00459a7aaa43c643d77ce6f16fb7ab037d3
 
-	  Change-Id: I21a514a0b56755c578a687f4867eacb8b59e23cf
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+2022-02-25 11:01 +0000 [37ece75677]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-05-22 07:29 +0000 [6f303335d3]  Jaco Kroon <jaco@uls.co.za>
+	* res_agi: Fix xmldocs bug with set music.
 
-	* func_lock: Add "dialplan locks show" cli command.
+	  The XML documentation for the SET MUSIC AGI
+	  command is invalid, as the parameter does not
+	  have a name and the on/off enum options for
+	  the on/off argument are listed separately, which
+	  is incorrect. The cumulative effect of these currently
+	  is that the Asterisk Wiki documentation for SET MUSIC
+	  is broken and external documentation generators crash
+	  on SET MUSIC due to the malformed documentation.
 
-	  For example:
+	  These issues are corrected so that the documentation
+	  can be successfully parsed as with other similar AGI
+	  commands.
 
-	  arthur*CLI> dialplan locks show
-	  func_lock locks:
-	  Name                                     Requesters Owner
-	  uls-autoref                              0          (unlocked)
-	  1 total locks listed.
+	  ASTERISK-29939 #close
+	  ASTERISK-28891 #close
 
-	  Obviously other potentially useful stats could be added (eg, how many
-	  times there was contention, how many times it failed etc ... but that
-	  would require keeping the stats and I'm not convinced that's worth the
-	  effort.  This was useful to troubleshoot some other issues so submitting
-	  it.
+	  Change-Id: I8c3d59897531bcbc401cbc7b00c9e2829dcb35f8
 
-	  Change-Id: Ib875e56feb49d523300aec5f36c635ed74843a9f
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+2022-02-18 03:19 +0000 [636d43caa3]  Boris P. Korzun <drtr0jan@yandex.ru>
 
-2021-05-22 07:42 +0000 [a3df5d7de8]  Jaco Kroon <jaco@uls.co.za>
+	* res_config_pgsql: Add text-type column check in require_pgsql()
 
-	* func_lock: Fix memory corruption during unload.
+	  Omit "unsupported column type 'text'" warning in logs while
+	  using text-type column in the PgSQL backend.
 
-	  AST_TRAVERSE accessess current as current = current->(field).next ...
-	  and since we free current (and ast_free poisons the memory) we either
-	  end up on a ast_mutex_lock to a non-existing lock that can never be
-	  obtained, or a segfault.
+	  ASTERISK-29924 #close
 
-	  Incidentally add logging in the "we have to wait for a lock to release"
-	  case, and remove an ineffective statement that sets memory that was just
-	  cleared by ast_calloc to zero.
+	  Change-Id: I48061a7d469426859670db07f1ed8af1eb814712
 
-	  Change-Id: Id19ba3d9867b23d0e6783b97e6ecd8e62698b8c3
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+2022-02-09 04:28 +0000 [2be01ba40b]  Kfir Itzhak <mastertheknife@gmail.com>
 
-2021-05-22 07:48 +0000 [6bd741b77d]  Jaco Kroon <jaco@uls.co.za>
+	* app_queue: Add QueueWithdrawCaller AMI action
 
-	* func_lock: Fix requesters counter in error paths.
+	  This adds a new AMI action called QueueWithdrawCaller.
+	  This AMI action makes it possible to withdraw a caller from a queue,
+	  in a safe and a generic manner.
+	  This can be useful for retrieving a specific call and
+	  dispatching it to a specific extension.
+	  It works by signaling the caller to exit the queue application
+	  whenever it can. Therefore, it is not guaranteed
+	  that the call will leave the queue.
 
-	  In two places we bail out with failure after we've already incremented
-	  the requesters counter, if this occured then it would effectively result
-	  in unload to wait indefinitely, thus preventing clean shutdown.
+	  ASTERISK-29909 #close
 
-	  Change-Id: I362a6c0dc424f736d4a9c733d818e72d19675283
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
-
-2021-05-25 10:36 +0000 [a611a0cd42]  Naveen Albert <asterisk@phreaknet.org>
+	  Change-Id: Ic15aa238e23b2884abdcaadff2fda7679e29b7ec
 
-	* app_originate: Allow setting Caller ID and variables
+2022-02-24 10:55 +0000 [fbde0186c7]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Caller ID can now be set on the called channel and
-	  Variables can now be set on the destination
-	  using the Originate application, just as
-	  they can be currently using call files
-	  or the Manager Action.
+	* ami: Improve substring parsing for disabled events.
 
-	  ASTERISK-29450
+	  ASTERISK_29853 added the ability to selectively disable
+	  AMI events on a global basis, but the logic for this uses
+	  strstr which means that events with names which are the prefix
+	  of another event, if disabled, could disable those events as
+	  well.
 
-	  Change-Id: Ia64cfe97d2792bcbf4775b3126cad662922a8b66
+	  Instead, we account for this possibility to prevent this
+	  undesired behavior from occuring.
 
-2021-06-10 16:24 +0000 [26059f8616]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK_29853
 
-	* menuselect: Fix description of several modules.
+	  Change-Id: Icccd1872602889806740971e4adf932f92466959
 
-	  The text description needs to be the last thing on the AST_MODULE_INFO
-	  line to be pulled in properly by menuselect.
+2022-03-02 08:57 +0000 [b40c4d59b1]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: I0c913e36fea8b661f42e56920b6c5513ae8fd832
+	* xml.c, config,c:  Add stylesheets and variable list string parsing
 
-2021-05-23 19:20 +0000 [a40e58a4da]  Naveen Albert <asterisk@phreaknet.org>
+	  Added functions to open, close, and apply XML Stylesheets
+	  to XML documents.  Although the presence of libxslt was already
+	  being checked by configure, it was only happening if xmldoc was
+	  enabled.  Now it's checked regardless.
 
-	* app_confbridge: New ConfKick() application
+	  Added ability to parse a string consisting of comma separated
+	  name/value pairs into an ast_variable list.  The reverse of
+	  ast_variable_list_join().
 
-	  Adds a new ConfKick() application, which may
-	  be used to kick a specific channel, all channels,
-	  or all non-admin channels from a specified
-	  conference bridge, similar to existing CLI and
-	  AMI commands.
+	  Change-Id: I1e1d149be22165a1fb8e88e2903a36bba1a6cf2e
 
-	  ASTERISK-29446
+2022-03-01 10:58 +0000 [9c36c055c1]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: I5d96b683880bfdd27b2ab1c3f2e897c5046ded9b
+	* xmldoc: Fix issue with xmlstarlet validation
 
-2021-06-02 08:11 +0000 [6873c5f3e4]  Naveen Albert <asterisk@phreaknet.org>
+	  Added the missing xml-stylesheet and Xinclude namespace
+	  declarations in pjsip_config.xml and pjsip_manager.xml.
 
-	* sip_to_pjsip: Fix missing cases
+	  Updated make_xml_documentation to show detailed errors when
+	  xmlstarlet is the validator.  It's now run once with the '-q'
+	  option to suppress harmless/expected messages and if it actually
+	  fails, it's run again without '-q' but with '-e' to show
+	  the actual errors.
 
-	  Adds the "auto" case which is valid with
-	  both chan_sip dtmfmode and chan_pjsip's
-	  dtmf_mode, adds subscribecontext to
-	  subscribe_context conversion, and accounts
-	  for cipher = ALL being invalid.
+	  Change-Id: I4bdc9d2ea6741e8d2e5eb82df60c68ccc59e1f5e
 
-	  ASTERISK-29459
+2022-02-20 14:16 +0000 [b5391ff691]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: Ie27d6606efad3591038000e5f3c34fa94730f6f2
+	* core: Config and XML tweaks needed for geolocation
 
-2021-06-02 08:25 +0000 [99573f9540]  Naveen Albert <asterisk@phreaknet.org>
+	  Added:
 
-	* res_pjsip_dtmf_info: Hook flash
+	  Replace a variable in a list:
+	  int ast_variable_list_replace_variable(struct ast_variable **head,
+	      struct ast_variable *old, struct ast_variable *new);
+	  Added test as well.
 
-	  Adds hook flash recognition support
-	  for application/hook-flash.
+	  Create a "name=value" string from a variable list:
+	  'name1="val1",name2="val2"', etc.
+	  struct ast_str *ast_variable_list_join(
+	      const struct ast_variable *head, const char *item_separator,
+	      const char *name_value_separator, const char *quote_char,
+	      struct ast_str **str);
+	  Added test as well.
 
-	  ASTERISK-29460
+	  Allow the name of an XML element to be changed.
+	  void ast_xml_set_name(struct ast_xml_node *node, const char *name);
 
-	  Change-Id: I1d060fa89a7cf41244c98f892fff44eb1c9738ea
+	  Change-Id: I330a5f63dc0c218e0d8dfc0745948d2812141ccb
 
-2021-05-20 09:51 +0000 [a861522467]  Naveen Albert <mail@interlinked.x10host.com>
+2022-02-14 07:31 +0000 [2e00b5edbd]  George Joseph <gjoseph@digium.com>
 
-	* app_confbridge: New option to prevent answer supervision
+	* Makefile: Allow XML documentation to exist outside source files
 
-	  A new user option, answer_channel, adds the capability to
-	  prevent answering the channel if it hasn't already been
-	  answered yet.
+	  Moved the xmldoc build logic from the top-level Makefile into
+	  its own script "make_xml_documentation" in the build_tools
+	  directory.
 
-	  ASTERISK-29440
+	  Created a new utility script "get_sourceable_makeopts", also in
+	  the build_tools directory, that dumps the top-level "makeopts"
+	  file in a format that can be "sourced" from shell sscripts.
+	  This allows scripts to easily get the values of common make
+	  build variables such as the location of the GREP, SED, AWK, etc.
+	  utilities as well as the AST* and library *_LIB and *_INCLUDE
+	  variables.
 
-	  Change-Id: I26642729d0345f178c7b8045506605c8402de54b
+	  Besides moving logic out of the Makefile, some optimizations
+	  were done like removing "third-party" from the list of
+	  subdirectories to be searched for documentation and changing some
+	  assignments from "=" to ":=" so they're only evaluated once.
+	  The speed increase is noticeable.
 
-2021-04-22 13:07 +0000 [8e2672d2a4]  George Joseph <gjoseph@digium.com>
+	  The makeopts.in file was updated to include the paths to
+	  REALPATH and DIRNAME.  The ./conifgure script was setting them
+	  but makeopts.in wasn't including them.
 
-	* res_pjsip_messaging: Refactor outgoing URI processing
+	  So...
 
-	   * Implemented the new "to" parameter of the MessageSend()
-	     dialplan application.  This allows a user to specify
-	     a complete SIP "To" header separate from the Request URI.
+	  With this change, you can now place documentation in any"c"
+	  source file AND you can now place it in a separate XML file
+	  altogether.  The following are examples of valid locations:
 
-	   * Completely refactored the get_outbound_endpoint() function
-	     to actually handle all the destination combinations that
-	     we advertized as supporting.
+	  res/res_pjsip.c
+	      Using the existing /*** DOCUMENTATION ***/ fragment.
 
-	   * We now also accept a destination in the same format
-	     as Dial()...  PJSIP/number@endpoint
+	  res/res_pjsip/pjsip_configuration.c
+	      Using the existing /*** DOCUMENTATION ***/ fragment.
 
-	   * Added lots of debugging.
+	  res/res_pjsip/pjsip_doc.xml
+	      A fully-formed XML file.  The "configInfo", "manager",
+	      "managerEvent", etc. elements that would be in the "c"
+	      file DOCUMENTATION fragment should be wrapped in proper
+	      XML.  Example for "somemodule.xml":
 
-	  ASTERISK-29404
-	  Reported by Brian J. Murrell
+	      <?xml version="1.0" encoding="UTF-8"?>
+	      <!DOCTYPE docs SYSTEM "appdocsxml.dtd">
+	      <docs>
+	          <configInfo>
+	          ...
+	          </configInfo>
+	      </docs>
 
-	  Change-Id: I67a485196d9199916468f7f98bfb9a0b993a4cce
+	  It's the "appdocsxml.dtd" that tells make_xml_documentation
+	  that this is a documentation XML file and not some other XML file.
+	  It also allows many XML-capable editors to do formatting and
+	  validation.
 
-2021-05-16 10:21 +0000 [9106c9d1f1]  Naveen Albert <mail@interlinked.x10host.com>
+	  Other than the ".xml" suffix, the name of the file is not
+	  significant.
 
-	* func_math: Three new dialplan functions
+	  As a start... This change also moves the documentation that was
+	  in res_pjsip.c to 2 new XML files in res/res_pjsip:
+	  pjsip_config.xml and pjsip_manager.xml.  This cut the number of
+	  lines in res_pjsip.c in half. :)
 
-	  Introduces three new dialplan functions, MIN and MAX,
-	  which can be used to calculate the minimum or
-	  maximum of up to two numbers, and ABS, an absolute
-	  value function.
+	  Change-Id: I486c16c0b5a44d7a8870008e10c941fb19b71ade
 
-	  ASTERISK-29431
+2022-02-17 10:26 +0000 [1950cec3fd]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: I2bda9269d18f9d54833c85e48e41fce0e0ce4d8d
+	* build: Refactor the earlier "basebranch" commit
 
-2021-05-19 13:45 +0000 [26a38c4084]  Ben Ford <bford@digium.com>
+	  Recap from earlier commit:  If you have a development branch for a
+	  major project that will receive gerrit reviews it'll probably be
+	  named something like "development/16/newproject" or a work branch
+	  based on that "development" branch.  That will necessitate
+	  setting "defaultbranch=development/16/newproject" in .gitreview.
+	  The make_version script uses that variable to construct the
+	  asterisk version however, which results in versions
+	  like "GIT-development/16/newproject-ee582a8c7b" which is probably
+	  not what you want.  It also constructs the URLs for downloading
+	  external modules with that version, which will fail.
 
-	* STIR/SHAKEN: Add Date header, dest->tn, and URL checking.
+	  Fast-forward:
 
-	  STIR/SHAKEN requires a Date header alongside the Identity header, so
-	  that has been added. Still on the outgoing side, we were missing the
-	  dest->tn section of the JSON payload, so that has been added as well.
-	  Moving to the incoming side, URL checking has been added to the public
-	  cert URL to ensure that it starts with http.
+	  The earlier attempt at adding a "basebranch" variable to
+	  .gitreview didn't work out too well in practice because changes
+	  were made to .gitreview, which is a checked-in file.  So, if
+	  you wanted to rebase your work branch on the base branch, rebase
+	  would attempt to overwrite your .gitreview with the one from
+	  the base branch and complain about a conflict.
 
-	  https://wiki.asterisk.org/wiki/display/AST/OpenSIPit+2021
+	  This is a slighltly different approach that adds three methods to
+	  determine the mainline branch:
 
-	  Change-Id: Idee5b1b5e45bc3b483b3070e46ce322dca5b3f1c
+	  1.  --- MAINLINE_BRANCH from the environment
 
-2021-05-24 13:38 +0000 [16e4a9d8cf]  Joshua C. Colp <jcolp@sangoma.com>
+	  If MAINLINE_BRANCH is already set in the environment, that will
+	  be used.  This is primarily for the Jenkins jobs.
 
-	* res_pjsip: On partial transport reload also move factories.
+	  2.  --- .develvars
 
-	  For connection oriented transports PJSIP uses factories to
-	  produce transports. When doing a partial transport reload
-	  we need to also move the factory of the transport over so
-	  that anything referencing the transport (such as an endpoint)
-	  has the factory available.
+	  Instead of storing the basebranch in .gitreview, it can now be
+	  stored in a non-checked-in ".develvars" file and keyed by the
+	  current branch.  So, if you were working on a branch named
+	  "new-feature-work" based on "development/16/new-feature" and wanted
+	   to push to that branch in Gerrit but wanted to pull the external
+	   modules for 16, you'd create the following .develvars file:
 
-	  ASTERISK-29441
+	  [branch "new-feature-work"]
+	      mainline-branch = 16
 
-	  Change-Id: Ieae0fb98eab2d9257cad996a1136e5a62d307161
+	  The .gitreview file would still look like:
 
-2021-05-20 08:18 +0000 [033c2a2283]  Naveen Albert <mail@interlinked.x10host.com>
+	  [gerrit]
+	  defaultbranch=development/16/new-feature
 
-	* func_volume: Add read capability to function.
+	  ...which would cause any reviews pushed from "new-feature-work" to
+	  go to the "development/16/new-feature" branch in Gerrit.
 
-	  Up until now, the VOLUME function has been write
-	  only, so that TX/RX values can be set but not
-	  read afterwards. Now, previously set TX/RX values
-	  can be read later.
+	  The key is that the .develvars file is NEVER checked in (it's been
+	  added to .gitignore).
 
-	  ASTERISK-29439
+	  3.  --- Well Known Development Branch
 
-	  Change-Id: Ia23e92fa2e755c36e9c8e69f2940d2703ccccb5f
+	  If you're actually working in a branch named like
+	  "development/<mainline_branch>/some-feature", the mainline branch
+	  will be parsed from it.
 
-2021-04-13 02:57 +0000 [59d15c4c2a]  Evgenios_Greek <jone1984@hotmail.com>
+	  4.  --- .gitreview
 
-	* stasis: Fix "FRACK!, Failed assertion bad magic number" when unsubscribing
+	  If none of the earlier conditions exist, the .gitreview
+	  "defaultbranch" variable will be used just as before.
 
-	  When unsubscribing from an endpoint technology a FRACK
-	  would occur due to incorrect reference counting. This fixes
-	  that issue, along with some other issues.
+	  Change-Id: I1cdeeaa0944bba3f2e01d7a2039559d0c266f8c9
 
-	  Fixed a typo in get_subscription when calling ao2_find as it
-	  needed to pass the endpoint ID and not the entire object.
+2022-02-23 07:58 +0000 [dd7db5c698]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  Fixed scenario where a subscription would get returned when
-	  it shouldn't have been when searching based on endpoint
-	  technology.
+	* jansson: Update bundled to 2.14 version.
 
-	  A doulbe unreference has also been resolved by only explicitly
-	  releasing the reference held by tech_subscriptions.
+	  ASTERISK-29353
 
-	  ASTERISK-28237 #close
-	  Reported by: Lucas Tardioli Silveira
+	  Change-Id: I4ea43eda1691565563a4c03ef37166952d211b2b
 
-	  Change-Id: Ia91b15f8e5ea68f850c66889a6325d9575901729
+2022-01-06 07:57 +0000 [27fb4fd5bc]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-05-20 02:15 +0000 [b21d4d1b87]  Joseph Nadiv <ynadiv@corpit.xyz>
+	* func_channel: Add lastcontext and lastexten.
 
-	* res_pjsip.c: Support endpoints with domain info in username
+	  Adds the lastcontext and lastexten channel fields to allow users
+	  to access previous dialplan execution locations.
 
-	  In multidomain environments, it is desirable to create
-	  PJSIP endpoints with the domain info in the endpoint name
-	  in pjsip_endpoint.conf.  This resulted in an error with
-	  registrations, NOTIFY, and OPTIONS packet generation.
+	  ASTERISK-29840 #close
 
-	  This commit will detect if there is an @ in the endpoint
-	  identifier and generate the URI accordingly so NOTIFY and
-	  OPTIONS From headers will generate correctly.
+	  Change-Id: Ib455fe300cc8e9a127686896ee2d0bd11e900307
 
-	  ASTERISK-28393
+2022-02-04 19:27 +0000 [3a3b8fbd9f]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I96f8d01dfdd5573ba7a28299e46271dd4210b619
+	* channel.c: Clean up debug level 1.
 
-2021-05-20 07:51 +0000 [3aed363716]  Joshua C. Colp <jcolp@sangoma.com>
+	  Although there are 10 debugs levels, over time,
+	  many current debug calls have come to use
+	  inappropriately low debug levels. In particular,
+	  a select few debug calls (currently all debug 1)
+	  can result in thousands of debug messages per minute
+	  for a single call.
 
-	* res_rtp_asterisk: Set correct raddr port on RTCP srflx candidates.
+	  This can adds a lot of noise to core debug
+	  which dilutes the value in having different
+	  debug levels in the first place, as these
+	  log messages are from the core internals are
+	  are better suited for higher debug levels.
 
-	  RTCP ICE candidates use a base address derived from the RTP
-	  candidate. The port on the base address was not being updated to
-	  the RTCP port.
+	  Some debugs levels are thus adjusted so that
+	  debug level 1 is not inappropriately overloaded
+	  with these extremely high-volume and general
+	  debug messages.
 
-	  This change sets the base port to the RTCP port and all is well.
+	  ASTERISK-29897 #close
 
-	  ASTERISK-29433
+	  Change-Id: I55a71598993552d3d64a401a35ee99474770d4b4
 
-	  Change-Id: Ide2d2115b307bfd3c2dfbc4d187515d724519040
+2022-02-17 13:47 +0000 [2ba5da15b0]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-05-25 05:38 +0000 [60ed1847b8]  Joshua C. Colp <jcolp@sangoma.com>
+	* configs, LICENSE: remove pbx.digium.com.
 
-	* asterisk: We've moved to Libera Chat!
+	  pbx.digium.com no longer accepts IAX2 calls and
+	  there are no plans for it to come back.
 
-	  Change-Id: I48c1933dd79b50ddc0a6793acec4754b4e95c575
+	  Accordingly, nonworking IAX2 URIs are removed from
+	  both the LICENSE file and the sample config.
 
-2021-05-19 13:13 +0000 [0f8e2174a7]  Jeremy Lainé <jeremy.laine@m4x.org>
+	  ASTERISK-29923 #close
 
-	* res_rtp_asterisk: make it possible to remove SOFTWARE attribute
+	  Change-Id: I257c54d4d812ed6b4bd4cbec2cd7ebe2b87b5bad
 
-	  By default Asterisk reports the PJSIP version in a SOFTWARE attribute
-	  of every STUN packet it sends. This may not be desired in a production
-	  environment, and RFC5389 recommends making the use of the SOFTWARE
-	  attribute a configurable option:
+2022-02-04 19:11 +0000 [c35e205bef]  Naveen Albert <asterisk@phreaknet.org>
 
-	  https://datatracker.ietf.org/doc/html/rfc5389#section-16.1.2
+	* documentation: Add since tag to xmldocs DTD
 
-	  This patch adds a `stun_software_attribute` yes/no option to make it
-	  possible to omit the SOFTWARE attribute from STUN packets.
+	  Adds the since tag to the documentation DTD so
+	  that individual applications, functions, etc.
+	  can now specify when they were added to Asterisk.
 
-	  ASTERISK-29434
+	  This tag is added at the individual application,
+	  function, etc. level as opposed to at the module
+	  level because modules can expand over time as new
+	  functionality is added, and granularity only
+	  to the module level would generally not be useful.
 
-	  Change-Id: Id3f2b1dd9584536ebb3a1d7e8395fd8b3e46860b
+	  This enables the ability to more easily determine
+	  when new functionality was added to Asterisk, down
+	  to minor version as opposed to just by major version.
+	  This makes it easier for users to write more portable
+	  dialplan if desired to not use functionality that may
+	  not be widely available yet.
 
-2021-04-15 10:43 +0000 [655ee680cd]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-29896 #close
 
-	* res_pjsip_outbound_authenticator_digest: Be tolerant of RFC8760 UASs
+	  Change-Id: Ibbb35c702d8038bdc3fd0a944fbfa69384cc15d5
 
-	  RFC7616 and RFC8760 allow more than one WWW-Authenticate or
-	  Proxy-Authenticate header per realm, each with different digest
-	  algorithms (including new ones like SHA-256 and SHA-512-256).
-	  Thankfully however a UAS can NOT send back multiple Authenticate
-	  headers for the same realm with the same digest algorithm.  The
-	  UAS is also supposed to send the headers in order of preference
-	  with the first one being the most preferred.  We're supposed to
-	  send an Authorization header for the first one we encounter for a
-	  realm that we can support.
+2022-01-13 08:37 +0000 [e26b57984f]  Naveen Albert <asterisk@phreaknet.org>
 
-	  The UAS can also send multiple realms, especially when it's a
-	  proxy that has forked the request in which case the proxy will
-	  aggregate all of the Authenticate headers and then send them all
-	  back to the UAC.
+	* asterisk: Add macro for curl user agent.
 
-	  It doesn't stop there though... Each realm can require a
-	  different username from the others.  There's also nothing
-	  preventing each digest algorithm from having a unique password
-	  although I'm not sure if that adds any benefit.
+	  Currently, each module that uses libcurl duplicates the standard
+	  Asterisk curl user agent.
 
-	  So now... For each Authenticate header we encounter, we have to
-	  determine if we support the digest algorithm and, if not, just
-	  skip the header.  We then have to find an auth object that
-	  matches the realm AND the digest algorithm or find a wildcard
-	  object that matches the digest algorithm. If we find one, we add
-	  it to the results vector and read the next Authenticate header.
-	  If the next header is for the same realm AND we already added an
-	  auth object for that realm, we skip the header. Otherwise we
-	  repeat the process for the next header.
+	  This adds a global macro for the Asterisk user agent used for
+	  curl requests to eliminate this duplication.
 
-	  In the end, we'll have accumulated a list of credentials we can
-	  pass to pjproject that it can use to add Authentication headers
-	  to a request.
+	  ASTERISK-29861 #close
 
-	  NOTE: Neither we nor pjproject can currently handle digest
-	  algorithms other than MD5.  We don't even have a place for it in
-	  the ast_sip_auth object. For this reason, we just skip processing
-	  any Authenticate header that's not MD5.  When we support the
-	  others, we'll move the check into the loop that searches the
-	  objects.
+	  Change-Id: I9fc37935980384b4daf96ae54fa3c9adb962ed2d
 
-	  Changes:
+2021-12-16 13:41 +0000 [1633410161]  Naveen Albert <asterisk@phreaknet.org>
 
-	   * Added a new API ast_sip_retrieve_auths_vector() that takes in
-	     a vector of auth ids (usually supplied on a call to
-	     ast_sip_create_request_with_auth()) and populates another
-	     vector with the actual objects.
+	* res_stir_shaken: refactor utility function
 
-	   * Refactored res_pjsip_outbound_authenticator_digest to handle
-	     multiple Authenticate headers and set the stage for handling
-	     additional digest algorithms.
+	  Refactors temp file utility function into file.c.
 
-	   * Added a pjproject patch that allows them to ignore digest
-	     algorithms they don't support.  This patch has already been
-	     merged upstream.
+	  ASTERISK-29809 #close
 
-	   * Updated documentation for auth objects in the XML and
-	     in pjsip.conf.sample.
+	  Change-Id: Ife478708c8f2b127239cb73c1755ef18c0bf431b
 
-	   * Although res_pjsip_authenticator_digest isn't affected
-	     by this change, some debugging and a testsuite AMI event
-	     was added to facilitate testing.
+2022-02-16 05:34 +0000 [39820e3561]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Discovered during OpenSIPit 2021.
+	* app_voicemail: Emit warning if asking for nonexistent mailbox.
 
-	  ASTERISK-29397
+	  Currently, if VoiceMailMain is called with a mailbox, if that
+	  mailbox doesn't exist, then the application silently falls back
+	  to prompting the user for the mailbox, as if no arguments were
+	  provided.
 
-	  Change-Id: I3aef5ce4fe1d27e48d61268520f284d15d650281
+	  However, if a specific mailbox is requested and it doesn't exist,
+	  then no warning at all is emitted.
 
-2021-04-14 09:44 +0000 [83c2a16b2e]  Joseph Nadiv <ynadiv@corpit.xyz>
+	  This fixes this behavior to now warn if a specifically
+	  requested mailbox could not be accessed, before falling back to
+	  prompting the user for the correct mailbox.
 
-	* res_pjsip_dialog_info_body_generator: Add LOCAL/REMOTE tags in dialog-info+xml
+	  ASTERISK-29920 #close
 
-	  RFC 4235 Section 4.1.6 describes XML elements that should be
-	  sent to subscribed endpoints to identify the local and remote
-	  participants in the dialog.
+	  Change-Id: Ib4093b88cd661a2cabc5d685777d4e2f0ebd20a4
 
-	  This patch adds this functionality to PJSIP by iterating through the
-	  ringing channels causing the NOTIFY, and inserts the channel info
-	  into the dialog so that information is properly passed to the endpoint
-	  in dialog-info+xml.
+2022-02-07 16:31 +0000 [a2aa881dcb]  Alexei Gradinari <alex2grad@gmail.com>
 
-	  ASTERISK-24601
-	  Patch submitted: Joshua Elson
-	  Modified by: Joseph Nadiv and Sean Bright
-	  Tested by: Joseph Nadiv
+	* res_pjsip_pubsub: fix Batched Notifications stop working
 
-	  Change-Id: I20c5cf5b45f34d7179df6573c5abf863eb72964b
+	  If Subscription refresh occurred between when the batched notification
+	  was scheduled and the serialized notification was to be sent,
+	  then new schedule notification task would never be added.
 
-2021-05-13 09:47 +0000 [bfc25e5de2]  Naveen Albert <mail@interlinked.x10host.com>
+	  There are 2 threads:
 
-	* app_voicemail: Configurable voicemail beep
+	  thread #1. ast_sip_subscription_notify is called,
+	  if notification_batch_interval then call schedule_notification.
+	  1.1. The schedule_notification checks notify_sched_id > -1
+	  not true, then
+	  send_scheduled_notify = 1
+	  notify_sched_id =
+	    ast_sched_add(sched, sub_tree->notification_batch_interval, sched_cb....
+	  1.2. The sched_cb pushes task serialized_send_notify to serializer
+	  and returns 0 which means no reschedule.
+	  1.3. The serialized_send_notify checks send_scheduled_notify if it's false
+	  the just returns. BUT notify_sched_id is still set, so no more ast_sched_add.
 
-	  Hitherto, VoiceMail() played a non-customizable beep tone to indicate
-	  the caller could leave a message. In some cases, the beep may not
-	  be desired, or a different tone may be desired.
+	  thread #2. pubsub_on_rx_refresh is called
+	  2.1 it pushes serialized_pubsub_on_refresh_timeout to serializer
+	  2.2. The serialized_pubsub_on_refresh_timeout calls pubsub_on_refresh_timeout
+	  which calls send_notify
+	  2.3. The send_notify set send_scheduled_notify = 0;
 
-	  To increase flexibility, a new option allows customization of the tone.
-	  If the t option is specified, the default beep will be overridden.
-	  Supplying an argument will cause it to use the specified file for the tone,
-	  and omitting it will cause it to skip the beep altogether. If the option
-	  is not used, the default behavior persists.
+	  The serialized_send_notify should always unset notify_sched_id.
 
-	  ASTERISK-29349
+	  ASTERISK-29904 #close
 
-	  Change-Id: I1c439c0011497e28a28067fc1cf1e654c8843280
+	  Change-Id: Ifc50c00b213c396509e10326a1ed89d8cf8c7875
 
-2021-05-13 10:32 +0000 [0ad3504ce0]  Naveen Albert <mail@interlinked.x10host.com>
+2022-02-01 09:59 +0000 [c12cb899de]  Alexei Gradinari <alex2grad@gmail.com>
 
-	* AMI: Add AMI event to expose hook flash events
+	* res_pjsip_pubsub: provide a display name for RLS subscriptions
 
-	  Although Asterisk can receive and propogate flash events, it currently
-	  provides no mechanism for doing anything with them itself.
+	  Whereas BLFs allow to show a display name for each RLS entry,
+	  the asterisk provides only the extension now.
+	  This is not end user friendly.
 
-	  This AMI event allows flash events to be processed by Asterisk.
-	  Additionally, AST_CONTROL_FLASH is included in a switch statement
-	  in channel.c to avoid throwing a warning when we shouldn't.
+	  This commit adds a new resource_list option, resource_display_name,
+	  to indicate whether display name of resource or the resource name being
+	  provided for RLS entries.
+	  If this option is enabled, the Display Name will be provided.
+	  This option is disabled by default to remain the previous behavior.
+	  If the 'event' set to 'presence' or 'dialog' the non-empty HINT name
+	  will be set as the Display Name.
+	  The 'message-summary' is not supported yet.
 
-	  ASTERISK-29380
+	  ASTERISK-29891 #close
 
-	  Change-Id: Ie17ffe65086e0282c88542e38eed6a461ec79e81
+	  Change-Id: Ic5306bd5a7c73d03f5477fe235e9b0f41c69c681
 
-2021-05-13 08:50 +0000 [7b82587dd6]  Naveen Albert <mail@interlinked.x10host.com>
+2022-02-18 06:09 +0000 [b1765c93e4]  Naveen Albert <asterisk@phreaknet.org>
 
-	* chan_sip: Expand hook flash recognition.
+	* func_db: Add validity check for key names when writing.
 
-	  Some ATAs send hook flash events as application/hook-flash, rather than a DTMF
-	  event. Now, we also recognize hook-flash as a flash event.
+	  Adds a simple sanity check for key names when users are
+	  writing data to AstDB. This captures four cases indicating
+	  malformed keynames that generally result in bad data going
+	  into the DB that the user didn't intend: an empty key name,
+	  a key name beginning or ending with a slash, and a key name
+	  containing two slashes in a row. Generally, this is the
+	  result of a variable being used in the key name being empty.
 
-	  ASTERISK-29370
+	  If a malformed key name is detected, a warning is emitted
+	  to indicate the bug in the dialplan.
 
-	  Change-Id: I1c3b82a040dff3affcd94bad8ce33edc90c04725
+	  ASTERISK-29925 #close
 
-2021-05-11 12:00 +0000 [6d5cac1d10]  Joshua C. Colp <jcolp@sangoma.com>
+	  Change-Id: Ifc08a9fe532a519b1b80caca1aafed7611d573bf
 
-	* pjsip: Add patch for resolving STUN packet lifetime issues.
+2022-01-13 19:37 +0000 [4722c8b70a]  Naveen Albert <asterisk@phreaknet.org>
 
-	  In some cases it was possible for a STUN packet to be destroyed
-	  prematurely or even destroyed partially multiple times.
+	* cli: Add core dump info to core show settings.
 
-	  This patch provided by Teluu fixes the lifetime of these
-	  packets and ensures they aren't partially destroyed multiple
-	  times.
+	  Adds two pieces of information to the core show settings command
+	  which are useful in the context of getting backtraces.
 
-	  https://github.com/pjsip/pjproject/pull/2709
+	  The first is to display whether or not Asterisk would generate
+	  a core dump if it were to crash.
 
-	  ASTERISK-29377
+	  The second is to show the current running directory of Asterisk.
 
-	  Change-Id: Ie842ad24ddf345e01c69a4d333023f05f787abca
+	  ASTERISK-29866 #close
 
-2021-05-13 10:13 +0000 [283fa3a93b]  Naveen Albert <mail@interlinked.x10host.com>
+	  Change-Id: Ic42c0a9ecc233381aad274d86c62808d1ebb4d83
 
-	* main/file.c: Don't throw error on flash event.
+2022-02-04 19:46 +0000 [335c69ead4]  Naveen Albert <asterisk@phreaknet.org>
 
-	  AST_CONTROL_FLASH isn't accounted for in a switch statement in file.c
-	  where it should be ignored. Adding this to the switch ensures a
-	  warning isn't thrown on RFC2833 flash events, since nothing's amiss.
+	* documentation: Adds missing default attributes.
 
-	  ASTERISK-29372
+	  The configObject tag contains a default attribute which
+	  allows the default value to be specified, if applicable.
+	  This allows for the default value to show up specially on
+	  the wiki in a way that is clear to users.
 
-	  Change-Id: I4fa549bfb7ba1894a4044de999ea124877422fbc
+	  There are a couple places in the tree where default values
+	  are included in the description as opposed to as attributes,
+	  which means these can't be parsed specially for the wiki.
+	  These are changed to use the attribute instead of being
+	  included in the text description.
 
-2021-05-12 21:20 +0000 [78d7862463]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK-29898 #close
 
-	* chan_pjsip: Correct misleading trace message
+	  Change-Id: I9d7ea08f50075f41459ea7b76654906b674ec755
 
-	  ASTERISK-29358 #close
+2022-02-05 06:39 +0000 [c9ef2b3b86]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I050daff67066873df4e8fc7f4bd977c1ca06e647
+	* app_mp3: Document and warn about HTTPS incompatibility.
 
-2021-04-26 17:00 +0000 [a84d34035a]  Ben Ford <bford@digium.com>
+	  mpg123 doesn't support HTTPS, but the MP3Player application
+	  doesn't document this or warn the user about this. HTTPS
+	  streams have become more common nowadays and users could
+	  reasonably try to play them without being aware they should
+	  use the HTTP stream instead.
 
-	* STIR/SHAKEN: Switch to base64 URL encoding.
+	  This adds documentation to note this limitation. It also
+	  throws a warning if users try to use the HTTPS stream to
+	  tell them to use the HTTP stream instead.
 
-	  STIR/SHAKEN encodes using base64 URL format. Currently, we just use
-	  base64. New functions have been added that convert to and from base64
-	  encoding.
+	  ASTERISK-29900 #close
 
-	  The origid field should also be an UUID. This means there's no reason to
-	  have it as an option in stir_shaken.conf, as we can simply generate one
-	  when creating the Identity header.
+	  Change-Id: Ie3b029be5258c5a701f71ed3b1a7a80d1e03b827
 
-	  https://wiki.asterisk.org/wiki/display/AST/OpenSIPit+2021
+2022-01-22 16:52 +0000 [0da713168d]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Icf094a2a54e87db91d6b12244c9f5ba4fc2e0b8c
+	* app_mf: Add max digits option to ReceiveMF.
 
-2021-05-11 12:26 +0000 [e0cbdfe063]  Ben Ford <bford@digium.com>
+	  Adds an option to the ReceiveMF application to allow specifying a
+	  maximum number of digits.
 
-	* STIR/SHAKEN: OPENSSL_free serial hex from openssl.
+	  Originally, this capability was not added to ReceiveMF as it was
+	  with ReceiveSF because typically a ST digit is used to denote that
+	  sending of digits is complete. However, there are certain signaling
+	  protocols which simply transmit a digit (such as Expanded In-Band
+	  Signaling) and for these, it's necessary to be able to read a
+	  certain number of digits, as opposed to until receiving a ST digit.
 
-	  We're getting the serial number of the certificate from openssl and
-	  freeing it with ast_free(), but it needs to be freed with OPENSSL_free()
-	  instead. Now we duplicate the string and free the one from openssl with
-	  OPENSSL_free(), which means we can still use ast_free() on the returned
-	  string.
+	  This capability is added as an option, as opposed to as a parameter,
+	  to remain compatible with existing usage (and not shift the
+	  parameters).
 
-	  https://wiki.asterisk.org/wiki/display/AST/OpenSIPit+2021
+	  ASTERISK-29877 #close
 
-	  Change-Id: Ia6e1a4028c1933a0e1d204b769ebb9f5a11f00ab
+	  Change-Id: I4229167c9aa69b87402c3c2a9065bd8dfa973a0b
 
-2021-04-21 11:12 +0000 [5e6508b56f]  Ben Ford <bford@digium.com>
+2022-01-09 07:32 +0000 [585c2d17bb]  Naveen Albert <asterisk@phreaknet.org>
 
-	* STIR/SHAKEN: Fix certificate type and storage.
+	* ami: Allow events to be globally disabled.
 
-	  During OpenSIPit, we found out that the public certificates must be of
-	  type X.509. When reading in public keys, we use the corresponding X.509
-	  functions now.
+	  The disabledevents setting has been added to the general section
+	  in manager.conf, which allows users to specify events that
+	  should be globally disabled and not sent to any AMI listeners.
 
-	  We also discovered that we needed a better naming scheme for the
-	  certificates since certificates with the same name would cause issues
-	  (overwriting certs, etc.). Now when we download a public certificate, we
-	  get the serial number from it and use that as the name of the cached
-	  certificate.
+	  This allows for processing of these AMI events to end sooner and,
+	  for frequent AMI events such as Newexten which users may not have
+	  any need for, allows them to not be processed. Additionally, it also
+	  cleans up core debug as previously when debug was 3 or higher,
+	  the debug was constantly spammed by "Analyzing AMI event" messages
+	  along with a complete dump of the event contents (often for Newexten).
 
-	  The configuration option public_key_url in stir_shaken.conf has also
-	  been renamed to public_cert_url, which better describes what the option
-	  is for.
+	  ASTERISK-29853 #close
 
-	  https://wiki.asterisk.org/wiki/display/AST/OpenSIPit+2021
+	  Change-Id: Id42b9a3722a1f460d745cad1ebc47c537fd4f205
 
-	  Change-Id: Ia00b20835f5f976e3603797f2f2fb19672d8114d
+2022-02-02 19:18 +0000 [3b1debb28b]  Mike Bradeen <mbradeen@sangoma.com>
 
-2021-04-22 13:07 +0000 [40bdfff73b]  George Joseph <gjoseph@digium.com>
+	* taskprocessor.c: Prevent crash on graceful shutdown
 
-	* Updates for the MessageSend Dialplan App
+	  When tps_shutdown is called as part of the cleanup process there is a
+	  chance that one of the taskprocessors that references the
+	  tps_singletons object is still running.  The change is to allow for
+	  tps_shutdown to check tps_singleton's container count and give the
+	  running taskprocessors a chance to finish.  If after
+	  AST_TASKPROCESSOR_SHUTDOWN_MAX_WAIT (10) seconds there are still
+	  container references we shutdown anyway as this is most likely a bug
+	  due to a taskprocessor not being unreferenced.
 
-	  Enhancements:
+	  ASTERISK-29365
 
-	   * The MessageSend dialplan application now takes an optional
-	     third argument that can set the message's "To" field on
-	     outgoing messages.  It's an alternative to using the
-	     MESSAGE(to) dialplan function.
+	  Change-Id: Ia932fc003d316389b9c4fd15ad6594458c9727f1
 
-	     NOTE: No channel driver currently implements this field.  A
-	     follow-on commit for res_pjsip_messaging will implement it for
-	     the chan_pjsip channel driver.
+2022-01-21 13:00 +0000 [b41440a179]  Alexei Gradinari <alex2grad@gmail.com>
 
-	   * To prevent confusion with the first argument, currently named
-	     "to", it's been renamed to "destination". Its function,
-	     creating the request URI, hasn't changed.
+	* app_queue: load queues and members from Realtime when needed
 
-	   * The documentation for MessageSend was updated to be
-	     more clear about the parameters and how they interact
-	     the MESSAGE() dialplan function.
+	  There are a lot of Queue AMI actions and Queue applications
+	  which do not load queue and queue members from Realtime.
 
-	   * With the rename of MessageSend's first parameter, and the fact
-	     that message.c references <info> elements in chan_sip.c,
-	     res_pjsip_messaging.c and res_xmpp, they each needed
-	     documentation updates to use MessageDestinationInfo instead of
-	     MessageToInfo.
+	  AMI actions
+	  QueuePause - if queue not in memory - response "Interface not found".
+	  QueueStatus/QueueSummary - if queue not in memory - empty response.
 
-	   * appdocsxml.dtd was updated to include a missing element
-	     declaration for "dataType".  This was showing up as an error
-	     in Eclipse's dtd editor.
+	  Applications:
+	  PauseQueueMember - if queue not in memory
+	  	Attempt to pause interface %s, not found
+	  UnpauseQueueMember - if queue not in memory
+	  	Attempt to unpause interface xxxxx, not found
 
-	   * Despite the changes in this commit, there should be
-	     no impact to current users of MessageSend.
+	  This patch adds a new function load_realtime_queues
+	  which loads queue and queue members for desired queue
+	  or all queues and all members if param 'queuename' is NULL or empty.
+	  Calls the function load_realtime_queues when needed.
 
-	  Change-Id: I6fb5b569657a02866a66ea352fd53d30d8ac965a
+	  Also this patch fixes leak of ast_config in function set_member_value.
 
-2021-04-30 15:21 +0000 [78f518622d]  Sean Bright <sean.bright@gmail.com>
+	  Also this patch fixes incorrect LOG_WARNING when pausing/unpausing
+	  already paused/unpaused member.
+	  The function ast_update_realtime returns 0 when no record modified.
+	  So 0 is not an error to warn about.
 
-	* translate.c: Avoid refleak when checking for a translation path
+	  ASTERISK-29873 #close
+	  ASTERISK-18416 #close
+	  ASTERISK-27597 #close
 
-	  Change-Id: Idbd61ff77545f4a78b06a5064b55112e774b70e6
+	  Change-Id: I554ee0eebde93bd8f49df7f84b74acb21edcb99c
 
-2021-04-28 07:17 +0000 [8faed04b01]  Joshua C. Colp <jcolp@sangoma.com>
+2022-02-07 10:55 +0000 [16fccf140d]  Sean Bright <sean.bright@gmail.com>
 
-	* chan_local: Skip filtering audio formats on removed streams.
+	* manager.c: Simplify AMI ModuleCheck handling
 
-	  When a stream topology is provided to chan_local when dialing
-	  it filters the audio formats down. This operation did not skip
-	  streams which were removed (that have no formats) resulting in
-	  calling being aborted.
+	  This code was needlessly complex and would fail to properly delimit
+	  the response message if LOW_MEMORY was defined.
 
-	  This change causes such streams to be skipped.
+	  Change-Id: Iae50bf09ef4bc34f9dc4b49435daa76f8b2c5b6e
 
-	  ASTERISK-29407
+2022-01-21 07:52 +0000 [427bee9beb]  Mark Petersen <bugs.digium.com@zombie.dk>
 
-	  Change-Id: I1de8b98727cb2d10f4bc287da0b5fdcb381addd6
+	* res_prometheus.c: missing module dependency
 
-2021-04-27 12:31 +0000 [95414fc918]  Sean Bright <sean.bright@gmail.com>
+	  added res_pjsip_outbound_registration to .requires in AST_MODULE_INFO
+	  which fixes issue with module crashes on load "FRACK!, Failed assertion"
 
-	* res_rtp_asterisk: More robust timestamp checking
+	  ASTERISK-29871
 
-	  We assume that a timestamp value of 0 represents an 'uninitialized'
-	  timestamp, but 0 is a valid value. Add a simple wrapper to be able to
-	  differentiate between whether the value is set or not.
+	  Change-Id: Ia0f49d048427a40e1b763296b834a52a03610096
 
-	  This also removes the fix for ASTERISK~28812 which should not be
-	  needed if we are checking the last timestamp appropriately.
+2022-02-03 15:48 +0000 [e1b050d8a3]  Sean Bright <sean.bright@gmail.com>
 
-	  ASTERISK-29030 #close
+	* res_pjsip.c: Correct minor typos in 'realm' documentation.
 
-	  Change-Id: Ie70d657d580d9a1f2877e25a6ef161c5ad761cf7
+	  Change-Id: I886936b808def5540d40071321e72f6bfa19063a
 
-2021-04-29 15:32 +0000  Asterisk Development Team <asteriskteam@digium.com>
+2022-01-31 12:52 +0000 [134cbebc1f]  Sean Bright <sean.bright@gmail.com>
 
-	* asterisk 18.4.0-rc1 Released.
+	* manager.c: Generate valid XML if attribute names have leading digits.
 
-2021-04-29 10:25 +0000 [1949d828b7]  Asterisk Development Team <asteriskteam@digium.com>
+	  The XML Manager Event Interface (amxml) now generates attribute names
+	  that are compliant with the XML 1.1 specification. Previously, an
+	  attribute name that started with a digit would be rendered as-is, even
+	  though attribute names must not begin with a digit. We now prefix
+	  attribute names that start with a digit with an underscore ('_') to
+	  prevent XML validation failures.
 
-	* Update CHANGES and UPGRADE.txt for 18.4.0
-2021-04-23 12:37 +0000 [d2dcd15bd8]  Sean Bright <sean.bright@gmail.com>
+	  This is not backwards compatible but my assumption is that compliant
+	  XML parsers would already have been complaining about this.
 
-	* res_pjsip.c: OPTIONS processing can now optionally skip authentication
+	  ASTERISK-29886 #close
 
-	  ASTERISK-27477 #close
+	  Change-Id: Icfaa56a131a082d803e9b7db5093806d455a0523
 
-	  Change-Id: I68f6715bba92a525149e35d142a49377a34a1193
+2022-02-01 10:09 +0000 [4126d703bf]  Sean Bright <sean.bright@gmail.com>
 
-2021-04-21 06:42 +0000 [dec44306cf]  Jean Aunis <jean.aunis@prescom.fr>
+	* build_tools/make_version: Fix bashism in comparison.
 
-	* translate.c: Take sampling rate into account when checking codec's buffer size
+	  In POSIX sh (which we indicate in the shebang), there is no ==
+	  operator.
 
-	  Up/down sampling changes the number of samples produced by a translation.
-	  This must be taken into account when checking the codec's buffer size.
+	  Change-Id: Ic03d38214d14cdf329b0ba272279a815bb532965
 
-	  ASTERISK-29328
+2022-01-21 14:08 +0000 [38c3c7f498]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: I9aebe2f8788e00321a7f5c47aa97c617f39e9055
+	* bundled_pjproject:  Add additional multipart search utils
 
-2021-04-25 04:45 +0000 [c2f4925ee0]  Joshua C. Colp <jcolp@sangoma.com>
+	  Added the following APIs:
+	  pjsip_multipart_find_part_by_header()
+	  pjsip_multipart_find_part_by_header_str()
+	  pjsip_multipart_find_part_by_cid_str()
+	  pjsip_multipart_find_part_by_cid_uri()
 
-	* svn: Switch to https scheme.
+	  Change-Id: I6aee3dcf59eb171f93aae0f0564ff907262ef40d
 
-	  Some versions of SVN seemingly don't follow the redirect
-	  to https.
+2022-01-07 04:01 +0000 [e505337065]  Mark Petersen <bugs.digium.com@zombie.dk>
 
-	  Change-Id: Ia7c76c18cb620bcf56f08e1211a7d80d321fe253
+	* chan_sip.c Fix pickup on channel that are in AST_STATE_DOWN
 
-2021-04-20 08:42 +0000 [5f3d96a765]  George Joseph <gjoseph@digium.com>
+	  resolve issue with pickup on device that uses "183" and not "180"
 
-	* res_pjsip:  Update documentation for the auth object
+	  ASTERISK-29832
 
-	  Change-Id: I2f76867ce02ec611964925159be099de83346e38
+	  Change-Id: I4c7d223870f8ce9a7354e0f73d4e4cb2e8b58841
 
-2021-04-02 07:21 +0000 [88aec107df]  George Joseph <gjoseph@digium.com>
+2022-01-26 07:56 +0000 [bfc4d63d15]  George Joseph <gjoseph@digium.com>
 
-	* bridge_channel_write_frame: Check for NULL channel
+	* build: Add "basebranch" to .gitreview
 
-	  There is a possibility, when bridge_channel_write_frame() is
-	  called, that the bridge_channel->chan will be NULL.  The first
-	  thing bridge_channel_write_frame() does though is call
-	  ast_channel_is_multistream() which had no check for a NULL
-	  channel and therefore caused a segfault. Since it's still
-	  possible for bridge_channel_write_frame() to write the frame to
-	  the other channels in the bridge, we don't want to bail before we
-	  call ast_channel_is_multistream() but we can just skip the
-	  multi-channel stuff.  So...
+	  If you have a development branch for a major project that
+	  will receive gerrit reviews it'll probably be named something
+	  like "development/16/newproject".  That will necessitate setting
+	  "defaultbranch=development/16/newproject" in .gitreview.  The
+	  make_version script uses that variable to construct the asterisk
+	  version however, which results in versions like
+	  "GIT-development/16/newproject-ee582a8c7b" which is probably not
+	  what you want.  Worse, since the download_externals script uses
+	  make_version to construct the URL to download the binary codecs
+	  or DPMA.  Since it's expecting a simple numeric version, the
+	  downloads will fail.
 
-	  bridge_channel_write_frame() only calls ast_channel_is_multistream()
-	  if bridge_channel->chan is not NULL.
+	  To get this to work, a new variable "basebranch" has been added
+	  to .gitreview and make_version has been updated to use that instead
+	  of defaultversion:
 
-	  As a safety measure, ast_channel_is_multistream() now returns
-	  false if the supplied channel is NULL.
+	  .gitreview:
+	  defaultbranch=development/16/myproject
+	  basebranch=16
 
-	  ASTERISK-29379
-	  Reported-by: Vyrva Igor
-	  Reported-by: Ross Beer
+	  Now git-review will send the reviews to the proper branch
+	  (development/16/myproject) but the version will still be
+	  constructed using the simple branch number (16).
 
-	  Change-Id: Idfe62dbea8c69813ecfd58e113a6620dc42352ce
+	  If "basebranch" is missing from .gitreview, make_version will
+	  fall back to using "defaultbranch".
 
-2021-04-01 10:38 +0000 [404533c149]  Sean Bright <sean.bright@gmail.com>
+	  Change-Id: I2941a3b21e668febeb6cfbc1a7bb51a67726fcc4
 
-	* loader.c: Speed up deprecation metadata lookup
+2022-01-31 07:09 +0000 [8d571ea6b5]  George Joseph <gjoseph@digium.com>
 
-	  Only use an XPath query once per module, then just navigate the DOM for
-	  everything else.
+	* res_pjsip_outbound_authenticator_digest: Prevent ABRT on cleanup
 
-	  Change-Id: Ia0336a7185f9180ccba4b6f631a00f9a22a36e92
+	  In dev mode, if you call pjsip_auth_clt_deinit() with an auth_sess
+	  that hasn't been initialized, it'll assert and abort.  If
+	  digest_create_request_with_auth() fails to find the proper
+	  auth object however, it jumps to its cleanup which does exactly
+	  that.  So now we no longer attempt to call pjsip_auth_clt_deinit()
+	  if we never actually initialized it.
 
-2021-04-01 08:39 +0000 [19eef2a6dc]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-29888
 
-	* res_prometheus: Clone containers before iterating
+	  Change-Id: Ib6171c25c9fe8e61cc8d11129e324c021bc30b62
 
-	  The channels, bridges and endpoints scrape functions were
-	  grabbing their respective global containers, getting the
-	  count of entries, allocating metric arrays based on
-	  that count, then iterating over the container.  If the
-	  global container had new objects added after the count
-	  was taken and the metric arrays were allocated, we'd run
-	  out of metric entries and attempt to write past the end
-	  of the arrays.
-
-	  Now each of the scape functions clone their respective
-	  global containers and all operations are done on the
-	  clone.  Since the clone is stable between getting the
-	  count and iterating over it, we can't run past the end
-	  of the metrics array.
-
-	  ASTERISK-29130
-	  Reported-By: Francisco Correia
-	  Reported-By: BJ Weschke
-	  Reported-By: Sébastien Duthil
-
-	  Change-Id: If0c8e40853bc0e9429f2ba9c7f5f358d90c311af
+2021-12-15 12:36 +0000 [386c5e495f]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-03-10 09:03 +0000 [a9a9864478]  Joshua C. Colp <jcolp@sangoma.com>
+	* cdr: allow disabling CDR by default on new channels
 
-	* loader: Output warnings for deprecated modules.
+	  Adds a new option, defaultenabled, to the CDR core to
+	  control whether or not CDR is enabled on a newly created
+	  channel. This allows CDR to be disabled by default on
+	  new channels and require the user to explicitly enable
+	  CDR if desired. Existing behavior remains unchanged.
 
-	  Using the information from the MODULEINFO XML we can
-	  now output useful information at the end of module
-	  loading for deprecated modules. This includes the
-	  version it was deprecated in, the version it will be
-	  removed in, and the replacement if available.
+	  ASTERISK-29808 #close
 
-	  ASTERISK-29339
+	  Change-Id: Ibb78c11974bda229bbb7004b64761980e0b2c6d1
 
-	  Change-Id: I2080dab97d2186be94c421b41dabf6d79a11611a
+2022-01-11 13:19 +0000 [70f8ea0d1a]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-03-22 15:22 +0000 [17c86dcfaa]  Kevin Harwell <kharwell@sangoma.com>
+	* res_tonedetect: Fixes some logic issues and typos
 
-	* res_rtp_asterisk: Fix standard deviation calculation
+	  Fixes some minor logic issues with the module:
 
-	  For some input to the standard deviation algorithm extremely large,
-	  and wrong numbers were being calculated.
+	  Previously, the OPT_END_FILTER flag was getting
+	  tested before options were parsed, so it could
+	  never evaluate to true (wrong ordering).
 
-	  This patch uses a new formula for correctly calculating both the
-	  running mean and standard deviation for the given inputs.
+	  Additionally, the initially parsed timeout (float)
+	  needs to be compared with 0, not the result int
+	  which is set afterwards (wrong variable).
 
-	  ASTERISK-29364 #close
+	  ASTERISK-29857 #close
 
-	  Change-Id: Ibc6e18be41c28bed3fde06d612607acc3fbd621f
+	  Change-Id: I0062bce3b391c15e5df7a714780eeaa96dd93d4c
 
-2021-03-29 17:40 +0000 [0ad1ff8a72]  Kevin Harwell <kharwell@sangoma.com>
+2022-01-11 12:33 +0000 [7ae8321925]  Naveen Albert <asterisk@phreaknet.org>
 
-	* res_rtp_asterisk: Don't count 0 as a minimum lost packets
+	* func_frame_drop: Fix typo referencing wrong buffer
 
-	  The calculated minimum lost packets represents the lowest number of
-	  lost packets missed during an RTCP report interval. Zero of course
-	  is the lowest, but the idea is that this value contain the lowest
-	  number of lost packets once some have been missed.
+	  In order to get around the issue of certain frames
+	  having names that could overlap, func_frame_drop
+	  surrounds names with commas for the purposes of
+	  comparison.
 
-	  This patch checks to make sure the number of lost packets over an
-	  interval is not zero before checking and setting the minimum value.
+	  The buffer is allocated and printed to properly,
+	  but the original buffer is used for comparison.
+	  In most cases, this wouldn't have had any effect,
+	  but that was not the intention behind the buffer.
+	  This updates the code to reference the modified
+	  buffer instead.
 
-	  Also, this patch updates the rtp lost packet test to check for
-	  packet loss over several reports vs one.
+	  ASTERISK-29854 #close
 
-	  Change-Id: I07d6e21cec61e289c2326138d6bcbcb3c3d5e008
+	  Change-Id: I430b52e14e712d0e62a23aa3b5644fe958b684a7
 
-2021-03-31 12:17 +0000 [1414b9cc57]  Kevin Harwell <kharwell@sangoma.com>
+2022-01-20 06:56 +0000 [7b15ced930]  Torrey Searle <tsearle@voxbone.com>
 
-	* res_rtp_asterisk: Statically declare rtp_drop_packets_data object
+	* res/res_rtp_asterisk: fix skip in rtp sequence numbers after dtmf
 
-	  This patch makes the drop_packets_data object static.
+	  When generating dtmfs, asterisk can incorrectly think packet loss
+	  occured during the dtmf generation, resulting in a jump in sequence
+	  numbers when forwarding voice frames resumes.  This patch forces
+	  asterisk to re-learn the expected sequence number after each DTMF
+	  to avoid this
 
-	  Change-Id: If4f9b21fa0c47d41a35b6b05941d978efb4da87b
+	  ASTERISK-29869 #close
 
-2021-03-29 17:52 +0000 [b0d828f14a]  Joshua C. Colp <jcolp@sangoma.com>
+	  Change-Id: Icc7de3d947b207b82c99d3c327af8095884df853
 
-	* res_rtp_asterisk: Only raise flash control frame on end.
+2022-01-13 16:31 +0000 [851a759619]  Kevin Harwell <kharwell@sangoma.com>
 
-	  Flash in RTP is conveyed the same as DTMF, just with a
-	  specific digit. In Asterisk however we do flash as a
-	  single control frame.
+	* res_http_websocket: Add a client connection timeout
 
-	  This change makes it so that only on end do we provide
-	  the flash control frame to the core. Previously we would
-	  provide a flash control frame on both begin and end,
-	  causing flash to work improperly.
+	  Previously there was no way to specify a connection timeout when
+	  attempting to connect a websocket client to a server. This patch
+	  makes it possible to now do such.
 
-	  ASTERISK-29373
+	  Change-Id: I5812f6f28d3d13adbc246517f87af177fa20ee9d
 
-	  Change-Id: I1accd9c6e859811336e670e698bd8bd124f33226
+2022-01-21 10:34 +0000 [ce91a0fdbc]  Sean Bright <sean.bright@gmail.com>
 
-2021-03-05 12:53 +0000 [b912b31853]  Kevin Harwell <kharwell@sangoma.com>
+	* build: Rebuild configure and autoconfig.h.in
 
-	* res_rtp_asterisk: Add a DEVMODE RTP drop packets CLI command
+	  autoconfigh.h.in was missed in the original review for this
+	  issue. Additionally it looks like I have newer pkg-config autoconf
+	  macros on my development machine.
 
-	  This patch makes it so when Asterisk is compiled in DEVMODE a CLI
-	  command is available that allows someone to drop incoming RTP
-	  packets. The command allows for dropping of packets once, or on a
-	  timed interval (e.g. drop 10 packets every 5 seconds). A user can
-	  also specify to drop packets by IP address.
+	  ASTERISK-29817
 
-	  Change-Id: I25fa7ae9bad6ed68e273bbcccf0ee51cae6e7024
+	  Change-Id: I3c85a4de82c5d7d6e0e23dad4c33bb650a86a57b
 
-2021-03-30 06:59 +0000 [65a4a3a4e6]  Joshua C. Colp <jcolp@sangoma.com>
+2021-12-08 15:14 +0000 [b79a571279]  Mike Bradeen <mbradeen@sangoma.com>
 
-	* res_pjsip: Give error when TLS transport configured but not supported.
+	* sched: fix and test a double deref on delete of an executing call back
 
-	  Change-Id: I058af496021ff870ccec2d8cbade637b348ab80b
+	  sched: Avoid a double deref when AST_SCHED_DEL_UNREF is called on an
+	  executing call-back. This is done by adding a new variable 'rescheduled'
+	  to the struct sched which is set in ast_sched_runq and checked in
+	  ast_sched_del_nonrunning. ast_sched_del_nonrunning is a replacement for
+	  now deprecated ast_sched_del which returns a new possible value -2
+	  if called on an executing call-back with rescheduled set. ast_sched_del
+	  is modified to call ast_sched_del_nonrunning to maintain existing code.
+	  AST_SCHED_DEL_UNREF is also updated to look for the -2 in which case it
+	  will not throw a warning or invoke refcall.
+	  test_sched: Add a new unit test sched_test_freebird that will check the
+	  reference count in the resolved scenario.
 
-2021-03-05 12:47 +0000 [15de2f1727]  Kevin Harwell <kharwell@sangoma.com>
+	  ASTERISK-29698
 
-	* time: Add timeval create and unit conversion functions
+	  Change-Id: Icfb16b3acbc29cf5b4cef74183f7531caaefe21d
 
-	  Added a TIME_UNIT enumeration, and a function that converts a
-	  string to one of the enumerated values. Also, added functions
-	  that create and initialize a timeval object using a specified
-	  value, and unit type.
+2022-01-04 03:11 +0000 [93d090147f]  Mark Petersen <bugs.digium.com@zombie.dk>
 
-	  Change-Id: Ic31a1c3262a44f77a5ef78bfc85dcf69a8d47392
+	* app_queue.c: Queue don't play "thank-you" when here is no hold time announcements
 
-2021-03-24 08:38 +0000 [35302efe73]  Sean Bright <sean.bright@gmail.com>
+	  if holdtime is (0 min, 0 sec) there is no hold time announcements
+	  we should then also not playing queue-thankyou
 
-	* app_queue: Add alembic migration to add ringinuse to queue_members.
+	  ASTERISK-29831
 
-	  ASTERISK-28356 #close
+	  Change-Id: Ic7e51dcde526b23f1cd8d24e1d1e2d81e10f9d2c
 
-	  Change-Id: I53a1bfdd3113d620bea88349019173a2f3f0ae39
+2022-01-19 16:33 +0000 [5875c7bb6c]  Luke Escude <luke@primevox.net>
 
-2021-03-28 10:47 +0000 [be3153346b]  Sean Bright <sean.bright@gmail.com>
+	* res_pjsip_sdp_rtp.c: Support keepalive for video streams.
 
-	* modules.conf: Fix more differing usages of assignment operators.
+	  ASTERISK-28890 #close
 
-	  I missed the changes in 18 and master in the previous review.
+	  Change-Id: Iad269a8dc36f892ede90fe8ceb3010560c0f70d1
 
-	  ASTERISK-24434 #close
+2021-11-10 21:40 +0000 [23be22abf4]  Michał Górny <mgorny@NetBSD.org>
 
-	  Change-Id: Ieb132b2a998ce96daa9c9acf26535a974b895876
+	* build_tools/make_version: Fix sed(1) syntax compatibility with NetBSD
 
-2021-03-24 10:52 +0000 [bbfb8f2b9d]  Ben Ford <bford@digium.com>
+	  Fix the sed(1) invocation used to process git-svn-id not to use "\s"
+	  that is a GNU-ism and is not supported by NetBSD sed.  As a result,
+	  this call did not work properly and make_version did output the full
+	  git-svn-id line rather than the revision.
 
-	* logger.conf.sample: Add more debug documentation.
+	  ASTERISK-29852
 
-	  Change-Id: Iff0e713f2120d8dce8e1e26924b99ed17f9d9dff
+	  Change-Id: Ie4b406e2748920643446851a0a252a4ca7245772
 
-2021-03-23 17:24 +0000 [31364fa4c8]  Sean Bright <sean.bright@gmail.com>
+2021-11-10 22:29 +0000 [2b490787eb]  Michał Górny <mgorny@NetBSD.org>
 
-	* queues.conf.sample: Correct 'context' documentation.
+	* main/utils: Implement ast_get_tid() for NetBSD
 
-	  ASTERISK-24631 #close
+	  Implement the ast_get_tid() function for NetBSD system.  NetBSD supports
+	  getting the TID via _lwp_self().
 
-	  Change-Id: I8bf8776906a72ee02f24de6a85345940b9ff6b6f
+	  ASTERISK-29850
 
-2021-03-23 15:15 +0000 [e27fa9eceb]  Sean Bright <sean.bright@gmail.com>
+	  Change-Id: If57fd3f9ea15ef5d010bfbdcbbbae9b379f72f8c
 
-	* app_queue.c: Remove dead 'updatecdr' code.
+2021-11-10 22:24 +0000 [dda02b8979]  Michał Górny <mgorny@NetBSD.org>
 
-	  Also removed the sample documentation, and some oddly-placed
-	  documentation about the timeout argument to the Queue() application
-	  itself. There is a large section on the timeout behavior below.
+	* main: Enable rdtsc support on NetBSD
 
-	  ASTERISK-26614 #close
+	  Enable the Linux rdtsc implementation on NetBSD as well.  The assembly
+	  works correctly there.
 
-	  Change-Id: I8f84e8304b50305b7c4cba2d9787a5d77c3a6217
+	  ASTERISK-29851
 
-2021-03-19 09:11 +0000 [a0009c807e]  Mark Murawski <markm@intellasoft.net>
+	  Change-Id: I460ad9b4d971913420ecb84186f5ba5ab03f6f37
 
-	* logger: Console sessions will now respect logger.conf dateformat= option
+2021-11-10 20:05 +0000 [6a879eea31]  Michał Górny <mgorny@NetBSD.org>
 
-	  The 'core' console (ie: asterisk -c) does read logger.conf and does
-	  use the dateformat= option.
+	* BuildSystem: Fix misdetection of gethostbyname_r() on NetBSD
 
-	  Whereas 'remote' consoles (ie: asterisk -r -T) does not read logger.conf
-	  and uses a hard coded dateformat option for printing received verbose messages:
-	    main/logger.c: static char dateformat[256] = "%b %e %T"
+	  Fix the configure script not to detect the presence of gethostbyname_r()
+	  on NetBSD incorrectly.  NetBSD includes it as an internal libc symbol
+	  that is not exposed in system headers and that is incompatible with
+	  other implementations.  In order to avoid misdetecting it, perform
+	  the symbol check only if the declaration is found in the public header
+	  first.
 
-	  This change will load logger.conf for each remote console session and
-	  use the dateformat= option to set the per-line timestamp for verbose messages
+	  ASTERISK-29817
 
-	  Change-Id: I3ea10990dbd920e9f7ce8ff771bc65aa7f4ea8c1
-	  ASTERISK-25358: #close
-	  Reported-by: Igor Liferenko
+	  Change-Id: Iafa359b09908251bcd299ff54be003ea129b9eda
 
-2021-03-19 15:57 +0000 [4393207751]  Sean Bright <sean.bright@gmail.com>
+2021-11-10 22:06 +0000 [710c8f8b29]  Michał Górny <mgorny@NetBSD.org>
 
-	* app_queue.c: Don't crash when realtime queue name is empty.
+	* include: Remove unimplemented HMAC declarations
 
-	  ASTERISK-27542 #close
+	  Remove the HMAC declarations from the includes.  They are
+	  not implemented nor used anywhere, and their presence breaks the build
+	  on NetBSD that delivers an incompatible hmac() function in <stdlib.h>.
 
-	  Change-Id: If0b9719380a25533d2aed1053cff845dc3a4854a
+	  ASTERISK-29818
 
-2021-03-18 11:14 +0000 [c78d0ce429]  George Joseph <gjoseph@digium.com>
+	  Change-Id: I0c4b88645e30174b1b63846a6b328625b69c2ea7
 
-	* res_pjsip_session: Make reschedule_reinvite check for NULL topologies
+2022-01-11 12:41 +0000 [27502b6dd2]  Naveen Albert <asterisk@phreaknet.org>
 
-	  When the check for equal topologies was added to reschedule_reinvite()
-	  it was assumed that both the pending and active media states would
-	  actually have non-NULL topologies.  We since discovered this isn't
-	  the case.
+	* frame.h: Fix spelling typo
 
-	  We now only test for equal topologies if both media states have
-	  non-NULL topologies.  The logic had to be rearranged a bit to make
-	  sure that we cloned the media states if their topologies were
-	  non-NULL but weren't equal.
+	  Fixes CNG description from "noice" to "noise".
 
-	  ASTERISK-29215
+	  ASTERISK-29855 #close
 
-	  Change-Id: I61313cca7fc571144338aac826091791b87b6e17
+	  Change-Id: Ie7cbbd7d72b426693df7447384ff8700318cd36d
 
-2021-03-19 04:56 +0000 [55c467eab1]  Joshua C. Colp <jcolp@sangoma.com>
+2022-01-11 12:46 +0000 [d35e292ae4]  Naveen Albert <asterisk@phreaknet.org>
 
-	* app_queue: Only send QueueMemberStatus if status changes.
+	* res_rtp_asterisk: Fix typo in flag test/set
 
-	  If a queue member was updated with the same status multiple
-	  times each time a QueueMemberStatus event would be sent
-	  which would be a duplicate of the previous.
+	  The code currently checks to see if an RFC3389
+	  warning flag is set, except if it is, it merely
+	  sets the flag again, the logic of which doesn't
+	  make any sense.
 
-	  This change makes it so that the QueueMemberStatus event is
-	  only sent if the status actually changes.
+	  This adjusts the if comparison to check if the
+	  flag has NOT been set, and if so, emit a notice
+	  log event and set the flag so that future frames
+	  do not cause an event to be logged.
 
-	  ASTERISK-29355
+	  ASTERISK-29856 #close
 
-	  Change-Id: I580c60d992a0a8f2bea8b91c868771b3b490d116
+	  Change-Id: Ib7098c947c63537d087a03b4646199fbb963f8e1
 
-2021-03-19 08:52 +0000 [ed2f637b47]  Joshua C. Colp <jcolp@sangoma.com>
+2022-01-18 08:04 +0000 [97ace6b816]  George Joseph <gjoseph@digium.com>
 
-	* core_unreal: Fix deadlock with T.38 control frames.
+	* bundled_pjproject: Fix srtp detection
 
-	  When using the ast_unreal_lock_all function no channel
-	  locks can be held before calling it.
+	  Reverted recent change that set '--with-external-srtp' instead
+	  of '--without-external-srtp'.  Since Asterisk handles all SRTP,
+	  we don't need it enabled in pjproject at all.
 
-	  This change unlocks the channel that indicate was
-	  called on before doing so and then relocks it afterwards.
+	  ASTERISK-29867
 
-	  ASTERISK-29035
+	  Change-Id: I2ce1bdd30abd21c062eac8f8fefe9b898787b801
 
-	  Change-Id: Id65016201b5f9c9519a216e250f9101c629e19e9
+2022-01-10 07:44 +0000 [b1dfc9c805]  George Joseph <gjoseph@digium.com>
 
-2021-03-01 17:32 +0000 [f213833514]  Joshua C. Colp <jcolp@sangoma.com>
+	* res_pjsip: Make message_filter and session multipart aware
 
-	* res_pjsip: Add support for partial transport reload.
+	  Neither pjsip_message_filter's filter_on_tx_message() nor
+	  res_pjsip_session's session_outgoing_nat_hook() were multipart
+	  aware and just assumed that an SDP would be the only thing in
+	  a message body.  Both were changed to use the new
+	  pjsip_get_sdp_info() function which searches for an sdp in
+	  both single- and multi- part message bodies.
 
-	  Some configuration items for a transport do not result in
-	  the underlying transport changing, but instead are just
-	  state we keep ourselves and use. It is perfectly reasonable
-	  to change these items.
+	  ASTERISK-29813
 
-	  These include local_net and external_* information.
+	  Change-Id: I8f5b8cfdc27f1d4bd3e7491ea9090951a4525c56
 
-	  ASTERISK-29354
+2022-01-12 11:12 +0000 [5d1407aa06]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: I027857ccfe4419f460243e562b5f098434b3d43a
+	* build: Fix issues building pjproject
 
-2021-03-13 05:01 +0000 [f47c5cbdf9]  Jaco Kroon <jaco@uls.co.za>
+	  The change to allow easier hacking on bundled pjproject created
+	  a few issues:
 
-	* menuselect: exit non-zero in case of failure on --enable|disable options.
+	  * The new Makefile was trying to run the bundled make even if
+	    PJPROJECT_BUNDLED=no.  third-party/Makefile now checks for
+	    PJPROJECT_BUNDLED and JANSSON_BUNDLED and skips them if they
+	    are "no".
 
-	  ASTERISK-29348
+	  * When building with bundled, config_site.h was being copied
+	    only if a full make or a "make main" was done.  A "make res"
+	    would fail all the pjsip modules because they couldn't find
+	    config_site.h.  The Makefile now copies config_site.h and
+	    asterisk_malloc_debug.h into the pjproject source tree
+	    when it's "configure" is performed.  This is how it used
+	    to be before the big change.
 
-	  Change-Id: I77e3466435f5a51a57538b29addb68d811af238d
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+	  ASTERISK-29858
 
-2021-03-17 10:28 +0000 [2e7fc84398]  Joshua C. Colp <jcolp@sangoma.com>
+	  Change-Id: I9427264fa3cb8b3f59a95e5f9693eac236a6f76d
 
-	* res_rtp_asterisk: Force resync on SSRC change.
+2022-01-06 13:05 +0000 [921ab52cf3]  George Joseph <gjoseph@digium.com>
 
-	  When an SSRC change occurs the timestamps are likely
-	  to change as well. As a result we need to reset the
-	  timestamp mapping done in the calc_rxstamp function
-	  so that they map properly from timestamp to real
-	  time.
+	* res_pjsip: Add utils for checking media types
 
-	  This previously occurred but due to packet
-	  retransmission support the explicit setting
-	  of the marker bit was not effective.
+	  Added two new functions to assist checking media types...
 
-	  ASTERISK-29352
+	  * ast_sip_are_media_types_equal compares two pjsip_media_types.
+	  * ast_sip_is_media_type_in tests if one media type is in a list
+	    of others.
 
-	  Change-Id: I2d4c8f93ea24abc1030196706de2d70facf05a5a
+	  Added static definitions for commonly used media types to
+	  res_pjsip.h.
 
-2021-03-10 08:05 +0000 [6aac148d59]  Joshua C. Colp <jcolp@sangoma.com>
+	  Changed several modules to use the new functions and static
+	  definitions.
 
-	* menuselect: Add ability to set deprecated and removed versions.
+	  ASTERISK_29813
+	  (not ready to close)
 
-	  The "deprecated_in" and "removed_in" information can now be
-	  set in MODULEINFO for a module and is then displayed in
-	  menuselect so users can be aware of when a module is slated
-	  to be deprecated and then removed.
+	  Change-Id: Ief77675235bd3bf00a6b095d4673fd878d0801b9
 
-	  ASTERISK-29337
+2022-01-12 07:16 +0000 [0d1b9e6baf]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: I6952889cf08e0e9e99cf8b43f99b3cef4688087a
+	* bundled_pjproject: Create generic pjsip_hdr_find functions
 
-2021-03-10 04:47 +0000 [be3e469f98]  Joshua C. Colp <jcolp@sangoma.com>
+	  pjsip_msg_find_hdr(), pjsip_msg_find_hdr_by_name(), and
+	  pjsip_msg_find_hdr_by_names() require a pjsip_msg to be passed in
+	  so if you need to search a header list that's not in a pjsip_msg,
+	  you have to do it yourself.  This commit adds generic versions of
+	  those 3 functions that take in the actual header list head instead
+	  of a pjsip_msg so if you need to search a list of headers in
+	  something like a pjsip_multipart_part, you can do so easily.
 
-	* documentation: Fix non-matching module support levels.
+	  Change-Id: I6f2c127170eafda48e5e0d5d4d187bcd52b4df07
 
-	  Some modules have a different support level documented in their
-	  MODULEINFO XML and Asterisk module definition. This change
-	  brings the two in sync for the modules which were not matching.
+2022-01-12 13:20 +0000 [65b2ddee26]  Sean Bright <sean.bright@gmail.com>
 
-	  ASTERISK-29336
+	* say.c: Prevent erroneous failures with 'say' family of functions.
 
-	  Change-Id: If2f819103d4a271e2e0624ef4db365e897fa3d35
+	  A regression was introduced in ASTERISK~29531 that caused 'say'
+	  functions to fail with file lists that would previously have
+	  succeeded. This caused affected channels to hang up where previously
+	  they would have continued.
 
-2021-03-10 08:18 +0000 [60fb559ccc]  Joshua C. Colp <jcolp@sangoma.com>
+	  We now explicitly check for the empty string to restore the previous
+	  behavior.
 
-	* xml: Allow deprecated_in and removed_in for MODULEINFO.
+	  ASTERISK-29859 #close
 
-	  ASTERISK-29337
+	  Change-Id: Ia2e5769868e2792313c2d7c07996efe009c6f8d5
 
-	  Change-Id: I2211b7da8d29369f8649aeabce07679da0787f2b
+2022-01-08 14:35 +0000 [5f59e0d36f]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-03-09 08:54 +0000 [60800b038a]  Joshua C. Colp <jcolp@sangoma.com>
+	* documentation: Document built-in system and channel vars
 
-	* xml: Embed module information into core XML documentation.
+	  Documentation for built-in special system and channel
+	  vars is currently outdated, and updating is a manual
+	  process since there is no XML documentation for these
+	  anywhere.
 
-	  This change embeds the MODULEINFO block of modules
-	  into the core XML documentation. This provides a shared
-	  mechanism for use by both menuselect and Asterisk for
-	  information and a definitive source of truth.
+	  This adds documentation for system vars to func_env
+	  and for channel vars to func_channel so that they
+	  appear along with the corresponding fields that would
+	  be accessed using a function.
 
-	  ASTERISK-29335
+	  ASTERISK-29848 #close
 
-	  Change-Id: Ifbfd5c700049cf320a3e45351ac65dd89bc99d90
+	  Change-Id: I6997f925c4a45fffe71321861f5898a8b7182fa9
 
-2021-03-11 17:23 +0000  Asterisk Development Team <asteriskteam@digium.com>
+2022-01-08 09:09 +0000 [fbaf74bd3a]  Naveen Albert <asterisk@phreaknet.org>
 
-	* asterisk 18.3.0-rc1 Released.
+	* pbx_variables: add missing ASTSBINDIR variable
 
-2021-03-11 10:33 +0000 [263f906af4]  Kevin Harwell <kharwell@sangoma.com>
+	  Every config variable in the directories
+	  section of asterisk.conf currently has a
+	  counterpart built-in variable containing
+	  the value of the config option, except
+	  for the last one, astsbindir, which should
+	  have an ASTSBINDIR variable.
 
-	* manager: Increase the non breaking AMI version number
+	  However, the actual corresponding ASTSBINDIR
+	  variable is missing in pbx_variables.c.
 
-	  ASTERISK~29244 added three new AMI events, so bump the version number.
+	  This adds the missing variable so that all
+	  the config options have their corresponding
+	  variable.
 
-	  Change-Id: I0e77fa36d38fb27dec3481d4ef08131330da0632
+	  ASTERISK-29847 #close
 
-2021-03-11 10:40 +0000 [0afd37e3b5]  Asterisk Development Team <asteriskteam@digium.com>
+	  Change-Id: I36006faf471825b36ebc8aa5e87a3bcb38d446fc
 
-	* Update CHANGES and UPGRADE.txt for 18.3.0
-2021-03-09 18:35 +0000 [f7bda066bb]  Joshua C. Colp <jcolp@sangoma.com>
+2021-11-30 16:35 +0000 [bc59b66de3]  George Joseph <gjoseph@digium.com>
 
-	* channel: Fix crash in suppress API.
+	* bundled_pjproject:  Make it easier to hack
 
-	  There exists an inconsistency with framehook usage
-	  such that it is only on reads that the frame should
-	  be freed, not on writes as well.
+	  There are times when you need to troubleshoot issues with bundled
+	  pjproject or add new features that need to be pushed upstream
+	  but...
 
-	  ASTERISK-29071
+	  * The source directory created by extracting the pjproject tarball
+	    is not scanned for code changes so you have to keep forcing
+	    rebuilds.
+	  * The source directory isn't a git repo so you can't easily create
+	    patches, do git bisects, etc.
+	  * Accidentally doing a make distclean will ruin your day by wiping
+	    out the source directory, and your changes.
+	  * etc.
 
-	  Change-Id: I5ef918ebe4debac8a469e8d43bf9d6b673e8e472
+	  This commit makes that easier.
+	  See third-party/pjproject/README-hacking.md for the details.
 
-2021-02-24 12:00 +0000 [23e41313a8]  Jaco Kroon <jaco@uls.co.za>
+	  ASTERISK-29824
 
-	* func_callerid+res_agi: Fix compile errors related to -Werror=zero-length-bounds
+	  Change-Id: Idb1251040affdab31d27cd272dda68676da9b268
 
-	  Change-Id: I75152cece8a00b7523d542e5ac22796f9595692b
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+2021-12-24 10:26 +0000 [0d62735f99]  Sean Bright <sean.bright@gmail.com>
 
-2021-02-24 12:34 +0000 [52707fba7f]  Jaco Kroon <jaco@uls.co.za>
+	* utils.c: Remove all usages of ast_gethostbyname()
 
-	* app.h: Fix -Werror=zero-length-bounds compile errors in dev mode.
+	  gethostbyname() and gethostbyname_r() are deprecated in favor of
+	  getaddrinfo() which we use in the ast_sockaddr family of functions.
 
-	  Change-Id: I5c104dc1f8417ccd3d01faf86e84ccbf89bc3b31
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+	  ASTERISK-29819 #close
 
-2021-03-06 16:57 +0000 [94debe5085]  Sean Bright <sean.bright@gmail.com>
+	  Change-Id: Ie277c0ef768d753b169c121ef570a71665692ab7
 
-	* app_dial.c: Only send DTMF on first progress event.
+2021-12-13 09:53 +0000 [262a4053ff]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29329 #close
+	* say.conf: fix 12pm noon logic
 
-	  Change-Id: Ic58e7a17f1ff3f785a5b21dced88682581149601
+	  Fixes 12pm noon incorrectly returning 0/a.m.
+	  Also fixes a misspelling typo in the config.
 
-2021-03-05 11:16 +0000 [262473c6d9]  Alexander Traud <pabstraud@compuserve.com>
+	  ASTERISK-29695 #close
 
-	* res_format_attr_*: Parameter Names are Case-Insensitive.
+	  Change-Id: Ie40f9618636eb4c483b449bd707a5dcffca5c406
 
-	  see RFC 4855:
-	  parameter names are case-insensitive both in media type strings and
-	  in the default mapping to the SDP a=fmtp attribute.
+2022-01-04 08:08 +0000 [3616dda066]  Sean Bright <sean.bright@gmail.com>
 
-	  This change is required for H.263+ because some implementations are
-	  known to use even mixed-case. This does not fix ASTERISK~29268 because
-	  H.264 was not fixed. This approach here lowers/uppers both parameter
-	  names and parameter values. H.264 needs a different approach because
-	  one of its parameter values is not case-insensitive:
-	  sprop-parameter-sets is Base64.
+	* pjproject: Fix incorrect unescaping of tokens during parsing
 
-	  Change-Id: Idf2a73457be231647aed3c87b1da197afba86892
+	  ASTERISK-29664 #close
 
-2021-03-05 11:45 +0000 [4fc0e16838]  Alexander Traud <pabstraud@compuserve.com>
+	  Change-Id: I29dcde52e9faeaf2609c604eada61c6a9e49d8f5
 
-	* chan_iax2: System Header strings is included via asterisk.h/compat.h.
+2021-12-30 07:02 +0000 [dc7bcd68e4]  Mark Petersen <bugs.digium.com@zombie.dk>
 
-	  The system header strings was included mistakenly with commit 3de0204.
-	  That header is included via asterisk.h and there via the compat.h.
+	* app_queue.c: Support for Nordic syntax in announcements
 
-	  Change-Id: I3dc49060e275295f785670c87cc65fd3c3abd24a
+	  adding support for playing the correct en/et for nordic languages
+	  by adding 'n' for neuter gender in the relevant ast_say_number
 
-2021-03-08 15:43 +0000 [3084084648]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK-29827
 
-	* modules.conf: Fix differing usage of assignment operators.
+	  Change-Id: I03ebc827d2f0dc95132ab2f42799893c70edc5b1
 
-	  ASTERISK-24434 #close
+2021-12-23 08:50 +0000 [138fbfa274]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I0144e8d65d878128da59dcf3df12ca8cee47d6db
+	* dsp: Add define macro for DTMF_MATRIX_SIZE
 
-2021-03-08 14:06 +0000 [e4cd7a7d0b]  Sean Bright <sean.bright@gmail.com>
+	  Adds the macro DTMF_MATRIX_SIZE to replace
+	  the magic number 4 sprinkled throughout
+	  dsp.c.
 
-	* strings.h: ast_str_to_upper() and _to_lower() are not pure.
+	  ASTERISK-29815 #close
 
-	  Because they modify their argument they are not pure functions and
-	  should not be marked as such, otherwise the compiler may optimize
-	  them away.
+	  Change-Id: Ie3bddb92c6b16204ece0f758009e9490eb33b9ba
 
-	  ASTERISK-29306 #close
+2022-01-03 11:10 +0000 [68f1e5d508]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Ibec03a08522dd39e8a137ece9bc6a3059dfaad5f
+	* ami: Add AMI event for Wink
 
-2021-03-08 17:16 +0000 [16e4d1f36f]  Sean Bright <sean.bright@gmail.com>
+	  Adds an AMI event for a wink frame.
 
-	* res_musiconhold.c: Plug ref leak caused by ao2_replace() misuse.
+	  ASTERISK-29830 #close
 
-	  ao2_replace() bumps the reference count of the object that is doing the
-	  replacing, which is not what we want. We just want to drop the old ref
-	  on the old object and update the pointer to point to the new object.
+	  Change-Id: I83e426de5e37baed79a4dbcc91e9e8d030ef1b56
 
-	  Pointed out by George Joseph in #asterisk-dev
+2021-12-15 08:23 +0000 [5b8d68d678]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Ie8167ed3d4b52b9d1ea2d785f885e8c27206743d
+	* cli: Add module refresh command
 
-2021-02-19 05:50 +0000 [90ef6a14a7]  Torrey Searle <tsearle@voxbone.com>
+	  Adds a command to the CLI to unload and then
+	  load a module. This makes it easier to perform
+	  these operations which are often done
+	  subsequently to load a new version of a module.
 
-	* res/res_rtp_asterisk: generate new SSRC on native bridge end
+	  "module reload" already refers to reloading of
+	  configuration, so the name "refresh" is chosen
+	  instead.
 
-	  For RTCP to work, we update the ssrc to be the one corresponding to
-	  the native bridge while active.  However when the bridge ends we
-	  should generate a new SSRC as the sequence numbers will not continue
-	  from the native bridge left off.
+	  ASTERISK-29807 #close
 
-	  ASTERISK-29300 #close
+	  Change-Id: I595f6f11774a0de2565a1fba38da22309ce93a2c
 
-	  Change-Id: I23334b6934d2bf6490bda4bbf6414d96b8d17d10
+2022-01-02 19:13 +0000 [80766059ef]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-03-01 15:35 +0000 [a9acbd19f3]  Joshua C. Colp <jcolp@sangoma.com>
+	* app_mp3: Throw warning on nonexistent stream
 
-	* sorcery: Add support for more intelligent reloading.
+	  Currently, the MP3Player application doesn't
+	  emit a warning if attempting to play a stream
+	  which no longer exists. This can be a common
+	  scenario as many mp3 streams are valid at some
+	  point but can disappear at any time.
 
-	  Some sorcery objects actually contain dynamic content
-	  that can change despite the underlying configuration
-	  itself not changing. A good example of this is the
-	  res_pjsip_endpoint_identifier_ip module which allows
-	  specifying hostnames. While the configuration may not
-	  change between reloads the DNS information of the
-	  hostnames can.
+	  Now a warning is thrown if attempting to play
+	  a nonexistent MP3 stream, instead of silently
+	  exiting.
 
-	  This change adds the ability for a sorcery object to be
-	  marked as having dynamic contents which is then taken
-	  into account when reloading by the sorcery file based
-	  config module. If there is an object with dynamic content
-	  then a reload will be forced while if there are none
-	  then the existing behavior of not reloading occurs.
+	  ASTERISK-29829 #close
 
-	  ASTERISK-29321
+	  Change-Id: I53a0bf1ed1740166655eb66fe7675f6f808bf535
 
-	  Change-Id: I9342dc55be46cc00204533c266a68d972760a0b1
+2021-12-13 08:29 +0000 [70bc0ff9d0]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-03-02 12:55 +0000 [269bb08ea2]  George Joseph <gjoseph@digium.com>
+	* documentation: Add missing AMI documentation
 
-	* res_pjsip_refer: Move the progress dlg release to a serializer
+	  Adds missing documentation for some channel,
+	  bridge, and queue events.
 
-	  Although the dlg session count was incremented in a pjsip servant
-	  thread, there's no guarantee that the last thread to unref this
-	  progress object was one.  Before we decrement, we need to make
-	  sure that this is either a servant thread or that we push the
-	  decrement to a serializer that is one.
+	  ASTERISK-24427
+	  ASTERISK-29515
 
-	  Because pjsip_dlg_dec_session requires the dialog lock, we don't
-	  want to wait on the task to complete if we had to push it to a
-	  serializer.
+	  Change-Id: I92b06b88c8cadc0155f95ebe3e870b3e795a8c64
 
-	  Change-Id: I8ff2d5d94be3ff04298394070434e22a7d3cbc41
+2021-11-15 16:13 +0000 [1ddaedeaf5]  Kevin Harwell <kharwell@sangoma.com>
 
-2021-03-03 12:31 +0000 [5f1c21e4ca]  Joshua C. Colp <jcolp@sangoma.com>
+	* tcptls.c: refactor client connection to be more robust
 
-	* res_pjsip_registrar: Include source IP and port in log messages.
+	  The current TCP client connect code, blocks and does not handle EINTR
+	  error case.
 
-	  When registering it can be useful to see the source IP address and
-	  port in cases where multiple devices are using the same endpoint
-	  or when anonymous is in use.
+	  This patch makes the client socket non-blocking while connecting,
+	  ensures a connect does not immediately fail due to EINTR "errors",
+	  and adds a connect timeout option.
 
-	  ASTERISK-29325
+	  The original client start call sets the new timeout option to
+	  "infinite", thus making sure old, orginal behavior is retained.
 
-	  Change-Id: Ie178a6f55f53f8473035854c411bc3d056e0a2e0
+	  ASTERISK-29746 #close
 
-2021-03-03 12:44 +0000 [682f7d9437]  Joshua C. Colp <jcolp@sangoma.com>
+	  Change-Id: I907571843a83e43c0742b95a64785f4411f02671
 
-	* asterisk: Update copyright.
+2021-12-13 10:59 +0000 [f7c4a3800c]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29326
+	* app_sf: Add full tech-agnostic SF support
 
-	  Change-Id: Ia95dbfb66e2d11ac4d1228444283bb2e4d77396a
+	  Adds tech-agnostic support for SF signaling
+	  by adding SF sender and receiver applications
+	  as well as Dial integration.
 
-2021-02-25 13:50 +0000 [77328142b4]  Ben Ford <bford@digium.com>
+	  ASTERISK-29802 #close
 
-	* AST-2021-006 - res_pjsip_t38.c: Check for session_media on reinvite.
+	  Change-Id: I7ec50752e9a661af639425e5d1e339f17411bcad
 
-	  When Asterisk sends a reinvite negotiating T38 faxing, it's possible a
-	  crash can occur if the response contains a m=image and zero port. The
-	  reinvite callback code now checks session_media to see if it is null or
-	  not before trying to access the udptl variable on it.
+2021-12-15 06:23 +0000 [a2ea233a6d]  Steve Davies <steve@one47.co.uk>
 
-	  ASTERISK-29305
+	* app_queue: Fix hint updates, allow dup. hints
 
-	  Change-Id: I1dfc51c5fa586e38579ede4bc228edee213ccaa9
+	  A previous patch for ASTERISK_29578 caused a 'leak' of
+	  extension state information across queues, causing the
+	  state of the first member of unrelated queues to be
+	  updated in addition to the correct member. Which queues
+	  and members depended on the order of queues in the
+	  iterator.
 
-2021-01-28 08:39 +0000 [0323293142]  Alexander Traud <pabstraud@compuserve.com>
+	  Additionally, it is possible to use the same 'hint:' on
+	  multiple queue members, so the update cannot break out
+	  of the update loop early when a match is found.
 
-	* res_format_attr_h263: Generate valid SDP fmtp for H.263+.
+	  ASTERISK-29806 #close
 
-	  Fixed:
-	  * RFC 4629 does not allow the value "0" for MPI, K, and N.
-	  * Allow value "0" for PAR.
-	  * BPP is printed only when specified because "0" has a meaning.
+	  Change-Id: If2c1d1cc2a752afd9286d79710fc818596e7a7ad
 
-	  New:
-	  * Added CPCF and MaxBR.
-	  * Some implementations provide CIF without MPI: a=fmtp:xx CIF;F=1
-	    Although a violation of RFC 3555 section 3, we can support that.
+2021-12-23 15:57 +0000 [3fd12f1aa3]  Sean Bright <sean.bright@gmail.com>
 
-	  Changed:
-	  * Resorts the CIFs from large to small which partly fixes ASTERISK~29267.
+	* say.c: Honor requests for DTMF interruption.
 
-	  Change-Id: I95a650c715007b8dde11a77cb37d9c6c123a441e
+	  SayAlpha, SayAlphaCase, SayDigits, SayMoney, SayNumber, SayOrdinal,
+	  and SayPhonetic all claim to allow DTMF interruption if the
+	  SAY_DTMF_INTERRUPT channel variable is set to a truthy value, but we
+	  are failing to break out of a given 'say' application if DTMF actually
+	  occurs.
 
-2021-02-24 07:04 +0000 [976b1a1d7a]  Joshua C. Colp <jcolp@sangoma.com>
+	  ASTERISK-29816 #close
 
-	* res_pjsip_nat: Don't rewrite Contact on REGISTER responses.
+	  Change-Id: I6a96e0130560831d2cb45164919862b9bcb6287e
 
-	  When sending a SIP response to an incoming REGISTER request
-	  we don't want to change the Contact header as it will
-	  contain the Contacts registered to the AOR and not our own
-	  Contact URI.
+2021-11-16 06:32 +0000 [dd41572f99]  Florentin Mayer <f.mayer@commend.com>
 
-	  ASTERISK-29235
+	* res_pjsip_sdp_rtp: Preserve order of RTP codecs
 
-	  Change-Id: I35a0723545281dd01fcd5cae497baab58720478c
+	  The ast_rtp_codecs_payloads functions do not preserve the order in which
+	  the payloads were specified on an incoming SDP media line. This leads to
+	  a problem with the codec negotiation functionality, as the format
+	  capabilities of the stream are extracted from the ast_rtp_codecs. This
+	  commit moves the ast_rtp_codec to ast_format conversion to the place
+	  where the order is still known.
 
-2021-03-03 07:32 +0000 [b43b81d953]  Joshua C. Colp <jcolp@sangoma.com>
+	  ASTERISK-28863
+	  ASTERISK-29320
 
-	* channel: Fix memory leak in suppress API.
+	  Change-Id: I3aabcfed3f379c36654f59c1872c313d0cb57e25
 
-	  A frame suppression API exists as part of channels
-	  which allows audio frames to or from a channel to
-	  be dropped. The MuteAudio AMI action uses this
-	  API to perform its job.
+2021-12-27 07:28 +0000 [f9e67945da]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  This API uses a framehook to intercept flowing
-	  audio and drop it when appropriate. It is the
-	  responsibility of the framehook to free the
-	  frame it is given if it changes the frame. The
-	  suppression API failed to do this resulting in
-	  a leak of audio frames.
+	* bridge: Unlock channel during Local peer check.
 
-	  This change adds the freeing of these frames.
+	  It's not safe to keep the channel locked while locking
+	  the peer Local channel, as it can result in a deadlock.
 
-	  ASTERISK-29071
+	  This change unlocks it during this time but keeps the
+	  bridge locked to ensure nothing changes about the bridge.
 
-	  Change-Id: Ie50acd454d672d36af914050c327d2e120d8ba7b
+	  ASTERISK-29821
 
-2021-01-27 14:01 +0000 [df8d335ad1]  Salah Ahmed <sahmed@voxbone.com>
+	  Change-Id: Ib68eb7037e5a479bcc2aceee77337cdde1fbdde6
 
-	* res_rtp_asterisk:  Check remote ICE reset and reset local ice attrb
+2021-11-07 09:32 +0000 [2b61440027]  Josh Soref <jsoref@gmail.com>
 
-	  This change will check is the remote ICE session got reset or not by
-	  checking the offered ufrag and password with session. If the remote ICE
-	  reset session then Asterisk reset its local ufrag and password to reject
-	  binding request with Old ufrag and Password.
+	* test_time.c: Tolerate DST transitions
 
-	  ASTERISK-29266
+	  When test_timezone_watch runs very near a DST transition,
+	  two time zones that would otherwise be expected to report the same
+	  time can differ because of the DST transition.
 
-	  Change-Id: I9c55e79a7af98a8fbb497d336b828ba41bc34eeb
+	  Instead of having the test fail when this happens, report the
+	  times, time zones, and dst flags.
 
-2021-01-07 08:25 +0000 [3286c04856]  Holger Hans Peter Freyther <holger@moiji-mobile.com>
+	  ASTERISK-29722
 
-	* pjsip: Generate progress (once) when receiving a 180 with a SDP
+	  Change-Id: Id59bdac8b277e14343ccdf0c99b89e92f79f316a
 
-	  ASTERISK-29105
+2021-12-14 11:39 +0000 [7728210352]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: If1615fe7115fe544ef974b044d3cea5c48b94a38
+	* bundled_pjproject:  Add more support for multipart bodies
 
-2021-02-28 03:24 +0000 [7b052ec965]  Nico Kooijman <nk@voclarion.nl>
+	  Adding upstream patch for pull request...
+	  https://github.com/pjsip/pjproject/pull/2920
+	  ---------------------------------------------------------------
 
-	* main: With Dutch language year after 2020 is not spoken in say.c
+	  sip_inv:  Additional multipart support (#2919)
 
-	  Implemented the english way of saying the year in ast_say_date_with_format_nl.
-	  Currently the numbers are spoken correctly until 2020 and stopped working
-	  this year.
+	  sip_inv.c:inv_check_sdp_in_incoming_msg() deals with multipart
+	  message bodies in rdata correctly. In the case where early media is
+	  involved though, the existing sdp has to be retrieved from the last
+	  tdata sent in this transaction. This, however, always assumes that
+	  the sdp sent is in a non-multipart body. While there's a function
+	  to retrieve the sdp from multipart and non-multpart rdata bodies,
+	  no similar function for tdata exists.  So...
 
-	  ASTERISK-29297 #close
-	  Reported-by: Jacek Konieczny
+	  * The existing pjsip_rdata_get_sdp_info2 was refactored to
+	    find the sdp in any body, multipart or non-multipart, and
+	    from either an rdata or tdata.  The new function is
+	    pjsip_get_sdp_info.  This new function detects whether the
+	    pjsip_msg->body->data is the text representation of the sdp
+	    from an rdata or an existing pjmedia_sdp_session object
+	    from a tdata, or whether pjsip_msg->body is a multipart
+	    body containing either of the two sdp formats.
 
-	  Change-Id: If5918eed5ab05df31df4dd23f08a909a60f6aba4
+	  * The exsting pjsip_rdata_get_sdp_info and pjsip_rdata_get_sdp_info2
+	    functions are now wrappers that get the body and Content-Type
+	    header from the rdata and call pjsip_get_sdp_info.
 
-2021-02-24 20:51 +0000 [dedfb334bd]  Nick French <nickfrench@gmail.com>
+	  * Two new wrappers named pjsip_tdata_get_sdp_info and
+	    pjsip_tdata_get_sdp_info2 have been created that get the body
+	    from the tdata and call pjsip_get_sdp_info.
 
-	* res_pjsip: dont return early from registration if init auth fails
+	  * inv_offer_answer_test.c was updated to test multipart scenarios.
 
-	  If set_outbound_initial_authentication_credentials() fails,
-	  handle_client_registration() bails early without creating or
-	  sending a register message.
+	  ASTERISK-29804
 
-	  [set_outbound_initial_authentication_credentials() failures
-	  can occur during the process of retrieving an oauth access
-	  token.]
+	  Change-Id: I483c7c3d413280c9e247a96ad581278347f9c71b
 
-	  The return from handle_client_registration is ignored, so
-	  returning an error doesn't do any good.
+2021-12-09 02:55 +0000 [cb44ceadec]  Frederic Van Espen <frederic.ve@gmail.com>
 
-	  This is a real problem when the registration request is a
-	  re-register, because then the registration will still be
-	  marked 'active' despite the re-register never being sent at all.
+	* ast_coredumper: Fix deleting results when output dir is set
 
-	  So instead, log a warning but let the registration be created
-	  and sent (and probably fail) and follow the normal registration
-	  failed retry/abort logic.
+	  When OUTPUTDIR is set to another directory and the
+	  --delete-results-after is set, the resulting txt files are
+	  not deleted.
 
-	  ASTERISK-29315 #close
+	  ASTERISK-29794 #close
 
-	  Change-Id: I2e03b1ea7fba1fa1a8279086aa4b17679e7fa7fa
+	  Change-Id: I1c0071f6809a1e3f5cfc455d6eb08378bc0d7286
 
-2021-02-23 10:14 +0000 [d5e73d2121]  Alexei Gradinari <alex2grad@gmail.com>
+2021-12-13 16:49 +0000 [cfcbf0adad]  Naveen Albert <asterisk@phreaknet.org>
 
-	* res_fax: validate the remote/local Station ID for UTF-8 format
+	* pbx_variables: initialize uninitialized variable
 
-	  If the remote Station ID contains invalid UTF-8 characters
-	  the asterisk fails to publish the Stasis and ReceiveFax status messages.
+	  The variable cp4 in a variable substitution function
+	  can potentially be used without being initialized
+	  currently. This causes Asterisk to no longer compile.
 
-	  json.c: Error building JSON from '{s: s, s: s}': Invalid UTF-8 string.
-	  0: /usr/sbin/asterisk(ast_json_vpack+0x98) [0x4f3f28]
-	  1: /usr/sbin/asterisk(ast_json_pack+0x8c) [0x4f3fcc]
-	  2: /usr/sbin/asterisk(ast_channel_publish_varset+0x2b) [0x57aa0b]
-	  3: /usr/sbin/asterisk(pbx_builtin_setvar_helper+0x121) [0x530641]
-	  4: /usr/lib64/asterisk/modules/res_fax.so(+0x44fe) [0x7f27f4bff4fe]
-	  ...
-	  stasis_channels.c: Error creating message
+	  This initializes cp4 to NULL to make the compiler
+	  happy.
 
-	  json.c: Error building JSON from '{s: s, s: s, s: s, s: s, s: s, s: s, s: o}': Invalid UTF-8 string.
-	  0: /usr/sbin/asterisk(ast_json_vpack+0x98) [0x4f3f28]
-	  1: /usr/sbin/asterisk(ast_json_pack+0x8c) [0x4f3fcc]
-	  2: /usr/lib64/asterisk/modules/res_fax.so(+0x5acd) [0x7f27f4c00acd]
-	  ...
-	  res_fax.c: Error publishing ReceiveFax status message
+	  ASTERISK-29803 #close
 
-	  This patch replaces the invalid UTF-8 Station IDs with an empty string.
+	  Change-Id: I392579cbb76db2795d5820c9427cf55fbcee9e72
 
-	  ASTERISK-29312 #close
+2021-12-08 05:24 +0000 [92cb1c0a59]  Mark Petersen <bugs.digium.com@zombie.dk>
 
-	  Change-Id: Ieb00b6ecf67db3bfca787649caa8517f29d987db
+	* app_queue.c: added DIALEDPEERNUMBER on outgoing channel
 
-2021-02-25 13:55 +0000 [6673c1b177]  Sean Bright <sean.bright@gmail.com>
+	  added that we set DIALEDPEERNUMBER on the outgoing channels
+	  so it is avalible in b(content^extension^line)
+	  this add the same behaviour as Dial
 
-	* app_page.c: Don't fail to Page if beep sound file is missing
+	  ASTERISK-29795
 
-	  ASTERISK-16799 #close
+	  Change-Id: Icbc589ea2066f0c401a892bf478f6b2fd44e62f6
 
-	  Change-Id: I40367b0d6dbf66a39721bde060c8b2d734a61cf4
+2021-11-15 15:35 +0000 [1c389faa31]  Kevin Harwell <kharwell@sangoma.com>
 
-2021-02-19 13:25 +0000 [15afabdf8e]  George Joseph <gjoseph@digium.com>
+	* http.c: Add ability to create multiple HTTP servers
 
-	* res_pjsip_refer: Refactor progress locking and serialization
+	  Previously, it was only possible to have one HTTP server in Asterisk.
+	  With this patch it is now possible to have multiple HTTP servers
+	  listening on different addresses.
 
-	  Although refer_progress_notify() always runs in the progress
-	  serializer, the pjproject evsub module itself can cause the
-	  subscription to be destroyed which then triggers
-	  refer_progress_on_evsub_state() to clean it up.  In this case,
-	  it's possible that refer_progress_notify() could get the
-	  subscription pulled out from under it while it's trying to use
-	  it.
+	  Note, this behavior has only been made available through an API call
+	  from within the TEST_FRAMEWORK. Specifically, this feature has been
+	  added in order to allow unit test to create/start and stop servers,
+	  if one has not been enabled through configuration.
 
-	  At one point we tried to have refer_progress_on_evsub_state()
-	  push the cleanup to the serializer and wait for its return before
-	  returning to pjproject but since pjproject calls its state
-	  callbacks with the dialog locked, this required us to unlock the
-	  dialog while waiting for the serialized cleanup, then lock it
-	  again before returning to pjproject. There were also still some
-	  cases where other callers of refer_progress_notify() weren't
-	  using the serializer and crashes were resulting.
-
-	  Although all callers of refer_progress_notify() now use the
-	  progress serializer, we decided to simplify the locking so we
-	  didn't have to unlock and relock the dialog in
-	  refer_progress_on_evsub_state().
-
-	  Now, refer_progress_notify() holds the dialog lock for its
-	  duration and since pjproject also holds the dialog lock while
-	  calling refer_progress_on_evsub_state() (which does the cleanup),
-	  there should be no more chances for the subscription to be
-	  cleaned up while still being used to send NOTIFYs.
+	  Change-Id: Ic5fb5f11e62c019a1c51310f4667b32a4dae52f5
 
-	  To be extra safe, we also now increment the session count on
-	  the dialog when we create a progress object and decrement
-	  the count when the progress is destroyed.
+2021-12-12 18:08 +0000 [b951821eb7]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29313
+	* app.c: Throw warnings for nonexistent options
 
-	  Change-Id: I97a8bb01771a3c85345649b8124507f7622a8480
+	  Currently, Asterisk doesn't throw warnings if options
+	  are passed into applications that don't accept them.
+	  This can confuse users if they're unaware that they
+	  are doing something wrong.
 
-2021-02-24 16:05 +0000 [be0a61bc3d]  Kevin Harwell <kharwell@sangoma.com>
+	  This adds an additional check to parse_options so that
+	  a warning is thrown anytime an option is parsed that
+	  doesn't exist in the parsing application, so that users
+	  are notified of the invalid usage.
 
-	* res_rtp_asterisk: Add packet subtype during RTCP debug when relevant
+	  ASTERISK-29801 #close
 
-	  For some RTCP packet types the report count is actually the packet's subtype.
-	  This was not being reflected in the packet debug output.
+	  Change-Id: Id029274a57135caca193c913307a63fd75e24679
 
-	  This patch makes it so for some RTCP packet types a "Packet Subtype" is
-	  now output in the debug replacing the "Reception reports" (i.e count).
+2021-12-08 12:07 +0000 [4f06de7cf8]  Mark Petersen <bugs.digium.com@zombie.dk>
 
-	  Change-Id: Id4f4b77bb37077a4c4f039abd6a069287bfefcb8
+	* app_voicemail.c: Support for Danish syntax in VM
 
-2021-02-15 13:02 +0000 [beb579bc99]  Boris P. Korzun <drtr0jan@yandex.ru>
+	  added support for playing the correct plural sound file
+	  dependen on where you have 1 or multipe messages
+	  based on the existing SE/NO code
 
-	* res_config_pgsql: Limit realtime_pgsql() to return one (no more) record.
+	  ASTERISK-29797
 
-	  Added a SELECT 'LIMIT' clause to realtime_pgsql() and refactored the function.
+	  Change-Id: I88aa814d02f3772bb80b474204b1ffb26fe438c2
 
-	  ASTERISK-29293 #close
+2021-11-17 15:39 +0000 [54761a41cd]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: If5a6d4b1072ea2e6e89059b21139d554a74b34f5
+	* app_sendtext: Add ReceiveText application
 
-2021-02-15 12:24 +0000 [83b0f5963f]  Ben Ford <bford@digium.com>
+	  Adds a ReceiveText application that can be used in
+	  conjunction with SendText. Currently, there is no
+	  way in Asterisk to receive text in the dialplan
+	  (or anywhere else, really). This allows for Asterisk
+	  to be the recipient of text instead of just the sender.
 
-	* res_pjsip_session.c: Check topology on re-invite.
+	  ASTERISK-29759 #close
 
-	  Removes an unnecessary check for the conditional that compares the
-	  stream topologies to see if they are equal to suppress re-invites. This
-	  was a problem when a Digium phone received an INVITE that offered codecs
-	  different than what it supported, causing Asterisk to send the
-	  re-invite.
+	  Change-Id: Ica2c354a42bff69f323a0493d3a7cd0fb129d52d
 
-	  ASTERISK-29303
+2021-12-11 20:11 +0000 [8ec13f06de]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I04dc91befb2387904e28a9aaeaa3bcdbcaa7fa63
+	* strings: Fix enum names in comment examples
 
-2021-02-23 05:28 +0000 [7ab53fce7a]  Jaco Kroon <jaco@uls.co.za>
+	  The enum values for ast_strsep_flags includes
+	  AST_STRSEP_STRIP. However, some comments reference
+	  AST_SEP_STRIP, which doesn't exist. This fixes
+	  these comments to use the correct value.
 
-	* res_odbc_transaction: correctly initialise forcecommit value from DSN.
+	  ASTERISK-29800 #close
 
-	  Also improve the in-process documentation to clarify that the value is
-	  initialised from the DSN and not default false, but that the DSN's value
-	  is default false if unset.
+	  Change-Id: If7bbd0c0e6226a211d25ddf9d1629347e2674943
 
-	  ASTERISK-29311 #close
+2021-11-20 14:37 +0000 [5c67a991c2]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: I46e2379f7b0656034442bce77cb37ccd4e61098d
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+	* pbx_variables: Increase parsing capabilities of MSet
 
-2021-02-16 12:33 +0000 [1af2a84c8b]  Joshua C. Colp <jcolp@sangoma.com>
+	  Currently MSet can only parse a maximum of 24 variables.
+	  If more variables are provided to MSet, the 24th variable
+	  will simply contain the remainder of the string and the
+	  remaining variables thereafter will never get set.
 
-	* res_pjsip_session: Always produce offer on re-INVITE without SDP.
+	  This increases the number of variables that can be parsed
+	  in one go from 24 to 99. Additionally, documentation is added
+	  since this limitation is currently undocumented and is
+	  confusing to users who encounter this limitation.
 
-	  When PJSIP receives a re-INVITE without an SDP offer the INVITE
-	  session library will first call the on_create_offer callback and
-	  if unavailable then use the active negotiated SDP as the offer.
+	  ASTERISK-29766 #close
 
-	  In some cases this would result in a different SDP then was
-	  previously used without an incremented SDP version number. The two
-	  known cases are:
+	  Change-Id: I3fe35b462dedec0a452fd9ea7f92c920a3939f16
 
-	  1. Sending an initial INVITE with a set of codecs and having the
-	  remote side answer with a subset. The active negotiated SDP would
-	  have the pruned list but would not have an incremented SDP version
-	  number.
+2021-11-23 20:21 +0000 [97f400100c]  Naveen Albert <asterisk@phreaknet.org>
 
-	  2. Using re-INVITE for unhold. We would modify the active negotiated
-	  SDP but would not increment the SDP version.
+	* chan_sip: Fix crash when accessing RURI before initiating outgoing call
 
-	  To solve these, and potential other unknown cases, the on_create_offer
-	  callback has now been implemented which produces a fresh offer with
-	  incremented SDP version number. This better fits within the model
-	  provided by the INVITE session library.
+	  Attempting to access ${CHANNEL(ruri)} in a pre-dial handler before
+	  initiating an outgoing call will cause Asterisk to crash. This is
+	  because a null field is accessed, resulting in an offset from null and
+	  subsequent memory access violation.
 
-	  ASTERISK-28452
+	  Since RURI is not guaranteed to exist, we now check if the base
+	  pointer is non-null before calculating an offset.
 
-	  Change-Id: I2d81048d54edcb80fe38fdbb954a86f0a58281a1
+	  ASTERISK-29772
 
-2021-02-10 11:59 +0000 [916d5d5e45]  Jaco Kroon <jaco@uls.co.za>
+	  Change-Id: Icd3b02f07256bbe6615854af5717074087b95a83
 
-	* app.h: Restore C++ compatibility for macro AST_DECLARE_APP_ARGS
+2021-10-25 16:19 +0000 [b64e894650]  Naveen Albert <asterisk@phreaknet.org>
 
-	  This partially reverts commit 3d1bf3c537bba0416f691f48165fdd0a32554e8a,
-	  specifically for app.h.
+	* func_json: Adds JSON_DECODE function
 
-	  This works with both gcc 9.3.0 and 10.2.0 now, both for C and C++ (as
-	  tested with external modules).
+	  Adds the JSON_DECODE function for parsing JSON in the
+	  dialplan. JSON parsing already exists in the Asterisk
+	  core and is used for many different things. This
+	  function exposes the basic parsing capability to
+	  the user in the dialplan, for instance, in conjunction
+	  with CURL for using API responses.
 
-	  ASTERISK-29287
+	  ASTERISK-29706 #close
 
-	  Change-Id: I5b9f02a9b290675682a1d13f1788fdda597c9fca
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+	  Change-Id: Iea60c49a7358dfdc2db60803cdc9a742f808ba2c
 
-2019-09-13 08:02 +0000 [985d3e4940]  Ivan Poddubnyi <ivan.poddubny@gmail.com>
+2021-11-17 15:16 +0000 [c3ff464864]  Naveen Albert <asterisk@phreaknet.org>
 
-	* app_queue: Fix conversion of complex extension states into device states
+	* configs: Updates to sample configs
 
-	  Queue members using dialplan hints as a state interface must handle
-	  INUSE+RINGING hint as RINGINUSE devstate, and INUSE + ONHOLD as INUSE.
+	  Includes some minor updates to extensions.conf
+	  and iax.conf. In particular, the demonstration
+	  of macros in extensions.conf is removed, as
+	  Macro is deprecated and will be removed soon.
+	  These examples have been replaced with examples
+	  demonstrating the usage of Gosub instead.
 
-	  ASTERISK-28369
+	  The older exten => ...,n syntax is also mostly
+	  replaced with the same keyword to demonstrate the
+	  newer, more concise way of defining extensions.
 
-	  Change-Id: I127e06943d4b4f1afc518f9e396de77449992b9f
+	  IAXTEL no longer exists, so this example is replaced
+	  with something more generic.
 
-2021-02-05 06:29 +0000 [1adf9368ee]  Alexander Traud <pabstraud@compuserve.com>
+	  Some documentation is also added to extensions.conf
+	  and iax.conf to clarify some of the new expanded
+	  encryption capabilities with IAX2.
 
-	* chan_sip: Filter pass-through audio/video formats away, again.
+	  ASTERISK-29758 #close
 
-	  Instead of looking for pass-through formats in the list of transcodable
-	  formats (which is going to find nothing), go through the result which
-	  is going to be the jointcaps of the tech_pvt of the channel. Finally,
-	  only with that list, ast_format_cap_remove(.) is going to succeed.
+	  Change-Id: I04fba9671aa1ee9ba1bd5027061f80bbe38e7b46
 
-	  This restores the behaviour of Asterisk 1.8. However, it does not fix
-	  ASTERISK_29282 because that issue report is about chan_sip and PJSIP.
-	  Here, only chan_sip is fixed because PJSIP does not even call
-	  ast_rtp_instance_available_formats -> ast_translate_available_format.
+2021-11-15 15:08 +0000 [23a4a12420]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Change-Id: Icade2366ac2b82935b95a9981678c987da2e8c34
+	* pbx: Add variable substitution API for extensions
 
-2021-02-17 14:51 +0000 [bee35fe04a]  Jaco Kroon <jaco@uls.co.za>
+	  Currently, variable substitution involving dialplan
+	  extensions is quite clunky since it entails obtaining
+	  the current dialplan location, backing it up, storing
+	  the desired variables for substitution on the channel,
+	  performing substitution, then restoring the original
+	  location.
 
-	* func_odbc:  Introduce minargs config and expose ARGC in addition to ARGn.
+	  In addition to being clunky, things could also go wrong
+	  if an async goto were to occur and change the dialplan
+	  location during a substitution.
 
-	  minargs enables enforcing of minimum count of arguments to pass to
-	  func_odbc, so if you're unconditionally using ARG1 through ARG4 then
-	  this should be set to 4.  func_odbc will generate an error in this case,
-	  so for example
+	  Fundamentally, there's no reason it needs to be done this
+	  way, so new API is added to allow for directly passing in
+	  the dialplan location for the purposes of variable
+	  substitution so we don't need to mess with the channel
+	  information anymore. Existing API is not changed.
 
-	  [FOO]
-	  minargs = 4
+	  ASTERISK-29745 #close
 
-	  and ODBC_FOO(a,b,c) in dialplan will now error out instead of using a
-	  potentially leaked ARG4 from Gosub().
+	  Change-Id: I23273bf27fa0efb64a606eebf9aa8e2f41a065e4
 
-	  ARGC is needed if you're using optional argument, to verify whether or
-	  not an argument has been passed, else it's possible to use a leaked ARGn
-	  from Gosub (app_stack).  So now you can safely do
-	  ${IF($[${ARGC}>3]?${ARGV}:default value)} kind of thing.
+2021-12-11 18:45 +0000 [6a6967bf0c]  Sean Bright <sean.bright@gmail.com>
 
-	  Change-Id: I6ca0b137d90b03f6aa9c496991f6cbf1518f6c24
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+	* CHANGES: Correct reference to configuration file.
 
-2021-01-13 14:05 +0000 [092628c982]  Sebastien Duthil <sduthil@wazo.community>
+	  Change-Id: I22a788ebf11168fff7fbf9ea956ebcd705ab63dd
 
-	* app_mixmonitor: Add AMI events MixMonitorStart, -Stop and -Mute.
+2021-09-21 19:18 +0000 [ee9eef492c]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK-29244
+	* app_mf: Add full tech-agnostic MF support
 
-	  Change-Id: I1862d58264c2c8b5d8983272cb29734b184d67c5
+	  Adds tech-agnostic support for MF signaling by adding
+	  MF sender and receiver applications as well as Dial
+	  integration.
 
-2021-02-09 11:25 +0000 [dbd8908f8d]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-29496-mf #do-not-close
 
-	* res_pjsip_refer: Always serialize calls to refer_progress_notify
+	  Change-Id: I61962b359b8ec4cfd05df877ddf9f5b8f71927a4
 
-	  refer_progress_notify wasn't always being called from the progress
-	  serializer.  This could allow clearing notification->progress->sub
-	  in one thread while another was trying to use it.
+2021-12-06 04:25 +0000 [67c4661fb0]  Alexander Traud <pabstraud@compuserve.com>
 
-	  * Instances where refer_progress_notify was being called in-line,
-	    have been changed to use ast_sip_push_task().
+	* xmldoc: Avoid whitespace around value for parameter/required.
 
-	  Change-Id: Idcf1934c4e873f2c82e2d106f8d9f040caf9fa1e
+	  Otherwise, the value 'false' was not found in the enumerated set of
+	  the XML DTD for the XML attribute 'required' in the XML element
+	  'parameter'. Therefore, DTD validation of the runtime XML failed.
 
-2021-02-01 15:24 +0000 [fad0cf12e6]  Kevin Harwell <kharwell@sangoma.com>
+	  ASTERISK-29790
 
-	* AST-2021-002: Remote crash possible when negotiating T.38
+	  Change-Id: Id13f230ad65a70dd8c2e3ae9ac85d1e841aed03e
 
-	  When an endpoint requests to re-negotiate for fax and the incoming
-	  re-invite is received prior to Asterisk sending out the 200 OK for
-	  the initial invite the re-invite gets delayed. When Asterisk does
-	  finally send the re-inivite the SDP includes streams for both audio
-	  and T.38.
+2021-12-04 02:36 +0000 [826233b550]  Alexander Traud <pabstraud@compuserve.com>
 
-	  This happens because when the pending topology and active topologies
-	  differ (pending stream is not in the active) in the delayed scenario
-	  the pending stream is appended to the active topology. However, in
-	  the fax case the pending stream should replace the active.
+	* progdocs: Fix Doxygen left-overs.
 
-	  This patch makes it so when a delay occurs during fax negotiation,
-	  to or from, the audio stream is replaced by the T.38 stream, or vice
-	  versa instead of being appended.
+	  Change-Id: I5b5cf9c9cbbe00ba8b379a8d162ac67445d39016
 
-	  Further when Asterisk sent the re-invite with both audio and T.38,
-	  and the endpoint responded with a declined T.38 stream then Asterisk
-	  would crash when attempting to change the T.38 state.
+2021-12-06 05:17 +0000 [12c45dd6a2]  Alexander Traud <pabstraud@compuserve.com>
 
-	  This patch also puts in a check that ensures the media state has a
-	  valid fax session (associated udptl object) before changing the
-	  T.38 state internally.
+	* xmldoc: Correct definition for XML element 'matchInfo'.
 
-	  ASTERISK-29203 #close
+	  ASTERISK-29791
 
-	  Change-Id: I407f4fa58651255b6a9030d34fd6578cf65ccf09
+	  Change-Id: I7c656498427fcadd0a5d61a54ff67e6036609725
 
-2021-01-26 11:09 +0000 [703158b903]  Alexander Traud <pabstraud@compuserve.com>
+2021-11-23 08:05 +0000 [f3b29c6aa8]  Alexander Traud <pabstraud@compuserve.com>
 
-	* rtp:  Enable srtp replay protection
+	* progdocs: Update Makefile.
 
-	  Add option "srtpreplayprotection" rtp.conf to enable srtp
-	  replay protection.
+	  In developer mode, use internal documentation as well.
+	  This should produce no warnings. Fix yours!
 
-	  ASTERISK-29260
-	  Reported by: Alexander Traud
+	  In noisy mode, output all possible warnings of Doxygen.
+	  This creates zillion of warnings. Double-check your current module!
 
-	  Change-Id: I5cd346e3c6b6812039d1901aa4b7be688173b458
-
-2020-12-28 06:43 +0000 [2770cc5872]  Ivan Poddubnyi <ivan.poddubny@gmail.com>
+	  Any warnings are in the file './doxygen.log'. Beside that, this change
+	  avoids deprecated parameters because the configuration file for Doxygen
+	  contains only those parameters which differ from the default. This
+	  avoids the need to update the file on each run. Furthermore, it adds
+	  AST_VECTOR to be expanded. Finally, the default name for that file is
+	  Doxyfile. Therefore, let us use that!
 
-	* res_pjsip_diversion: Fix adding more than one histinfo to Supported
+	  ASTERISK-26991
+	  ASTERISK-20259
 
-	  New responses sent within a PJSIP sessions are based on those that were
-	  sent before. Therefore, adding/modifying a header once causes it to be
-	  sent on all responses that follow.
+	  Change-Id: I4129092a199d5e24c319a09cd088614b121015af
 
-	  Sending 181 Call Is Being Forwarded many times first adds "histinfo"
-	  duplicated more and more, and eventually overflows past the array
-	  boundary.
+2021-12-03 07:38 +0000 [f6df28ce87]  Alexander Traud <pabstraud@compuserve.com>
 
-	  This commit adds a check preventing adding "histinfo" more than once,
-	  and skipping it if there is no more space in the header.
+	* res_pjsip_sdp_rtp: Do not warn on unknown sRTP crypto suites.
 
-	  Similar overflow situations can also occur in res_pjsip_path and
-	  res_pjsip_outbound_registration so those were also modified to
-	  check the bounds and suppress duplicate Supported values.
+	  res_sdp_crypto_parse_offer(.) emits many log messages already.
 
-	  ASTERISK-29227
-	  Reported by: Ivan Poddubny
+	  ASTERISK-29785
 
-	  Change-Id: Id43704a1f1a0293e35cc7f844026f0b04f2ac322
+	  Change-Id: I1a191ebe4fec1102946d4e31887e5197ca02dfe8
 
-2020-12-11 14:49 +0000 [5a6f2f913b]  Sean Bright <sean.bright@gmail.com>
+2021-11-30 14:16 +0000 [e9cac5f4bf]  Sean Bright <sean.bright@gmail.com>
 
-	* res_rtp_asterisk.c: Fix signed mismatch that leads to overflow
+	* channel: Short-circuit ast_channel_get_by_name() on empty arg.
 
-	  ASTERISK-29205 #close
+	  We know that passing a NULL or empty argument to
+	  ast_channel_get_by_name() will never result in a matching channel and
+	  will always result in an error being emitted, so just short-circuit
+	  out in that case.
 
-	  Change-Id: Ib7aa65644e8df76e2378d7613ee7cf751b9d0bea
+	  ASTERISK-28219 #close
 
-2021-02-05 05:26 +0000 [acb7ce4fe7]  Joshua C. Colp <jcolp@sangoma.com>
+	  Change-Id: I88eadc748e9c6996fc17467b0a05881bbfd00bce
 
-	* pjsip: Make modify_local_offer2 tolerate previous failed SDP.
+2021-10-26 16:12 +0000 [59fcd1e7e2]  Mike Bradeen <mbradeen@sangoma.com>
 
-	  If a remote side is broken and sends an SDP that can not be
-	  negotiated the call will be torn down but there is a window
-	  where a second 183 Session Progress or 200 OK that is forked
-	  can be received that also attempts to negotiate SDP. Since
-	  the code marked the SDP negotiation as being done and complete
-	  prior to this it assumes that there is an active local and remote
-	  SDP which it can modify, while in fact there is not as the SDP
-	  did not successfully negotiate. Since there is no local or remote
-	  SDP a crash occurs.
+	* res_rtp_asterisk: Addressing possible rtp range issues
 
-	  This patch changes the pjmedia_sdp_neg_modify_local_offer2
-	  function to no longer assume that a previous SDP negotiation
-	  was successful.
+	  res/res_rtp_asterisk.c: Adding 1 to rtpstart if it is deteremined
+	  that rtpstart was configured to be an odd value. Also adding a loop
+	  counter to prevent a possible infinite loop when looking for a free
+	  port.
 
-	  ASTERISK-29196
+	  ASTERISK-27406
 
-	  Change-Id: I22de45916d3b05fdc2a67da92b3a38271ee5949e
+	  Change-Id: I90f07deef0716da4a30206e9f849458b2dbe346b
 
-2021-01-11 14:20 +0000 [62e2dd484d]  Ben Ford <bford@digium.com>
+2021-08-24 09:56 +0000 [a8b2692836]  Mark Petersen <bugs.digium.com@zombie.dk>
 
-	* core_unreal: Fix T.38 faxing when using local channels.
+	* apps/app_dial.c: HANGUPCAUSE reason code for CANCEL is set to AST_CAUSE_NORMAL_CLEARING
 
-	  After some changes to streams and topologies, receiving fax through
-	  local channels stopped working. This change adds a stream topology with
-	  a stream of type IMAGE to the local channel pair and allows fax to be
-	  received.
+	  changed that when we recive a CANCEL that we set HANGUPCAUSE to AST_CAUSE_NORMAL_CLEARING
 
-	  ASTERISK-29035 #close
+	  ASTERISK-28053
+	  Reported by: roadkill
 
-	  Change-Id: Id103cc5c9295295d8e68d5628e76220f8f17e9fb
+	  Change-Id: Ib653aec2282f55b59d87484391cc07c8e6612b89
 
-2021-02-02 02:33 +0000 [57d130d3aa]  Boris P. Korzun <drtr0jan@yandex.ru>
+2021-11-19 02:54 +0000 [a85f2bf34d]  Alexander Traud <pabstraud@compuserve.com>
 
-	* format_wav: Support of MIME-type for wav16
+	* res: Fix for Doxygen.
 
-	  Provided a support of a MIME-type for wav16. Added new MIME-type
-	  for classic wav.
+	  These are the remaining issues found in /res.
 
-	  ASTERISK-29275 #close
+	  ASTERISK-29761
 
-	  Change-Id: I749bda287ba1ab20c1e0af5e4c0153817d47873b
+	  Change-Id: I572e6019c422780dde5ce8448b6c85c77af6046d
 
-2021-02-05 02:33 +0000 [45e48e387c]  Alexander Traud <pabstraud@compuserve.com>
+2021-11-08 18:30 +0000 [e93fb874b4]  Dustin Marquess <jailbird@fdf.net>
 
-	* chan_sip: Allow [peer] without audio (text+video).
+	* res_fax_spandsp: Add spandsp 3.0.0+ compatibility
 
-	  Two previous commits, 620d9f4 and 6d980de, allow to set up a call
-	  without audio, again. That was introduced originally with commit f04d5fb
-	  but changed and broke over time. The original commit missed one
-	  scenario: A [peer] section in sip.conf, which does not allow audio at
-	  all. In that case, chan_sip rejected the call, although even when the
-	  requester offered no audio. Now, chan_sip does not check whether there
-	  is no audio format but checks whether there is no format in general. In
-	  other words, if there is at least one format to offer, the call succeeds.
+	  Newer versions of spandsp did refactoring of code to add new features
+	  like color FAXing. This refactoring broke backwards compatibility.
+	  Add support for the new version while retaining support for 0.0.6.
 
-	  However, to prevent calls with no-audio, chan_sip still rejects calls
-	  when both call parties (caller = requester of the call *and* callee =
-	  [peer] section in sip.conf) included audio. In such a case, it is
-	  expected that the call should have audio.
+	  ASTERISK-29729 #close
 
-	  ASTERISK-29280
+	  Change-Id: I3bd74550604ebcf0304528d647fa39abc62fbaa1
 
-	  Change-Id: I0fb74faf51ef22a60c10b467df6a4d1c1943b73e
+2021-11-19 09:47 +0000 [9440f6ec58]  Alexander Traud <pabstraud@compuserve.com>
 
-2021-01-28 12:02 +0000 [28f187d6c5]  George Joseph <gjoseph@digium.com>
+	* main: Fix for Doxygen.
 
-	* chan_iax2.c: Require secret and auth method if encryption is enabled
+	  ASTERISK-29763
 
-	  If there's no secret specified for an iax2 peer and there's no secret
-	  specified in the dial string, Asterisk will crash if the auth method
-	  requested by the peer is MD5 or plaintext.  You also couldn't specify
-	  a default auth method in the [general] section of iax.conf so if you
-	  don't have static peers defined and just use the dial string, Asterisk
-	  will still crash even if you have a secret specified in the dial string.
+	  Change-Id: Ib8359e3590a9109eb04a5376559d040e5e21867e
 
-	  * Added logic to iax2_call() and authenticate_reply() to print
-	    a warning and hanhup the call if encryption is requested and
-	    there's no secret or auth method.  This prevents the crash.
+2021-11-27 13:11 +0000 [cc025026b7]  Alexander Traud <pabstraud@compuserve.com>
 
-	  * Added the ability to specify a default "auth" in the [general]
-	    section of iax.conf.
+	* progdocs: Fix for Doxygen, the hidden parts.
 
-	  ASTERISK-29624
-	  Reported by: N A
+	  ASTERISK-29779
 
-	  Change-Id: I5928e16137581f7d383fcc7fa04ad96c919e6254
+	  Change-Id: If338163488498f65fa7248b60e80299c0a928e4b
 
-2021-02-03 12:53 +0000 [24d6adfe99]  Sean Bright <sean.bright@gmail.com>
+2021-11-12 10:05 +0000 [affe7ee879]  Alexander Traud <pabstraud@compuserve.com>
 
-	* app_read: Release tone zone reference on early return.
+	* progdocs: Fix grouping for latest Doxygen.
 
-	  Change-Id: I350939f2220f9e5d44ddf4c8d9a4c99fde4d169a
+	  Since Doxygen 1.8.16, a special comment block is required. Otherwise
+	  (pure C comment), the group command is ignored. Additionally, several
+	  unbalanced group commands were fixed.
 
-2021-01-27 11:42 +0000 [87ad1138ff]  Alexander Traud <pabstraud@compuserve.com>
+	  ASTERISK-29732
 
-	* chan_sip: Set up calls without audio (text+video), again.
+	  Change-Id: I4687857b9d56e6f44fd440b73af156691660202e
 
-	  The previous commit 6d980de fixed this issue in the core of Asterisk.
-	  With that, each channel technology can be used without audio
-	  theoretically. Practically, the channel-technology driver chan_sip
-	  turned out to have an invalid check preventing that. chan_sip tested
-	  whether there is at least one audio format. However, chan_sip has to
-	  test whether there is at least one format. More cannot be tested while
-	  requesting chan_sip because only the [general] capabilities but not the
-	  [peer] caps are known yet. And the [peer] caps might not be a subset or
-	  show any intersection with the [general] caps. This change here fixes
-	  this.
+2021-11-25 12:41 +0000 [24a04054ad]  Naveen Albert <asterisk@phreaknet.org>
 
-	  The original commit f04d5fb, thirteen years ago, contained a software
-	  bug as it passed ANY audio capability to the channel-technology driver.
-	  Instead, it should have passed NO audio format. Therefore, this
-	  addressed issue here was not noticed in Asterisk 1.6.x and Asterisk 1.8.
-	  Then, Asterisk 10 changed that from ANY to NO, but nobody reported since
-	  then.
+	* documentation: Standardize examples
 
-	  ASTERISK-29265
+	  Most examples in the XML documentation use the
+	  example tag to demonstrate examples, which gets
+	  parsed specially in the Wiki to make it easier
+	  to follow for users.
 
-	  Change-Id: Ic16a3bf13cd1b5c4fc4041ed74961177d96b600f
+	  This fixes a few modules to use the example
+	  tag instead of vanilla para tags to bring them
+	  in line with the standard syntax.
 
-2021-01-22 09:12 +0000 [088816284a]  Dan Cropp <dan@amtelco.com>
+	  ASTERISK-29777 #close
 
-	* chan_pjsip, app_transfer: Add TRANSFERSTATUSPROTOCOL variable
+	  Change-Id: I9acb6cc5faf1d220e73c6dd28592371d768d279b
 
-	  When a Transfer/REFER is executed, TRANSFERSTATUSPROTOCOL variable is
-	  0 when no protocl specific error
-	  SIP example of failure, 3xx-6xx for the SIP error code received
+2021-11-28 14:52 +0000 [2478bfcff9]  Sean Bright <sean.bright@gmail.com>
 
-	  This allows applications to perform actions based on the failure
-	  reason.
+	* config.c: Prevent UB in ast_realtime_require_field.
 
-	  ASTERISK-29252 #close
-	  Reported-by: Dan Cropp
+	  A backend's implementation of the realtime 'require' function may call
+	  va_arg() and then fail, leaving the va_list in an undefined
+	  state. Pass a copy of the va_list instead.
 
-	  Change-Id: Ia6a94784b4925628af122409cdd733c9f29abfc4
+	  ASTERISK-29771 #close
 
-2021-01-22 07:38 +0000 [176274caa4]  Mark Petersen <bugs.digium.com@zombie.dk>
+	  Change-Id: I555565a72af84e96d49f62fe8cb66ba5a78461f4
 
-	* res/res_pjsip.c: allow user=phone when number contain *#
+2021-11-01 10:40 +0000 [d374d63ef8]  Naveen Albert <asterisk@phreaknet.org>
 
-	  if From number contain * or # asterisk will not add user=phone
+	* app_voicemail: Refactor email generation functions
 
-	  Currently only number that uses AST_DIGIT_ANYNUM can have "user=phone" but the validation should use AST_DIGIT_ANY
-	  this is a problem when you want to send call to ISUP
-	  as they will disregard the From header and either replace From with anonymous or with p-asserted-identity
+	  Refactors generic functions used for email generation
+	  into utils.c so that they can be used by multiple
+	  modules, including app_voicemail and app_minivm,
+	  to avoid code duplication.
 
-	  ASTERISK-29261
-	  Reported by: Mark Petersen
-	  Tested by: Mark Petersen
+	  ASTERISK-29715 #close
 
-	  Change-Id: I3307bdbf757582740bfee4110e85f7b6c9291cc4
+	  Change-Id: I1de0ed3483623e9599711129edc817c45ad237ee
 
-2021-01-22 02:54 +0000 [f64ddf3db3]  Alexander Traud <pabstraud@compuserve.com>
+2021-11-25 10:34 +0000 [ecffdab059]  Alexander Traud <pabstraud@compuserve.com>
 
-	* channel: Set up calls without audio (text+video), again.
+	* stir/shaken: Avoid a compiler extension of GCC.
 
-	  ASTERISK-29259
+	  ASTERISK-29776
 
-	  Change-Id: Ib6a6550e0e08355745d66da8e60ef49e81f9c6c5
+	  Change-Id: I86e5eca66fb775a5744af0c929fb269e70575a73
 
-2021-01-21 13:28 +0000 [4c154f3431]  Alexander Traud <pabstraud@compuserve.com>
+2021-11-23 07:12 +0000 [1230369b71]  Alexander Traud <pabstraud@compuserve.com>
 
-	* chan_sip: SDP: Reject audio streams correctly.
+	* progdocs: Remove outdated references in doxyref.h.
 
-	  This completes the fix for ASTERISK_24543. Only when the call is an
-	  outgoing call, consult and append the configured format capabilities
-	  (p->caps). When all audio formats got rejected the negotiated format
-	  capabilities (p->jointcaps) contain no audio formats for incoming
-	  calls. This is required when there are other accepted media streams.
+	  ASTERISK-29773
 
-	  ASTERISK-29258
+	  Change-Id: Ica93160d9158cc0e80c5fda829b80d1b49a6b9b9
 
-	  Change-Id: I8bab31c7f3f3700dce204b429ad238a524efebb9
+2021-10-28 02:28 +0000 [4b3c75ca31]  Jaco Kroon <jaco@uls.co.za>
 
-2021-01-22 11:17 +0000 [7c0fbaf010]  Ivan Poddubnyi <ivan.poddubny@gmail.com>
+	* logger: use __FUNCTION__ instead of __PRETTY_FUNCTION__
 
-	* main/frame: Add missing control frame names to ast_frame_subclass2str
+	  This avoids a few long-name overflows, at the cost of less instructive
+	  names in the case of C++ (specifically overloaded functions and class
+	  methods).  This in turn is offset against the fact that we're logging
+	  the filename and line numbers in any case.
 
-	  Log proper control frame names instead of "Unknown control '14'", etc.
+	  Change-Id: I54101a0bb5f8cb9ef63ec12c5e0d4c8edafff9ed
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
 
-	  Change-Id: I1724f2f4d1b064b25a5c93a7da0cb03be5143935
+2021-11-20 06:05 +0000 [38f9000fcb]  Alexander Traud <pabstraud@compuserve.com>
 
-2021-01-23 07:15 +0000 [f1c88a497b]  Boris P. Korzun <drtr0jan@yandex.ru>
+	* xmldoc: Fix for Doxygen.
 
-	* res_musiconhold: Add support of various URL-schemes by MoH.
+	  ASTERISK-29765
 
-	  Provided a support of variuos URL-schemes for res_musiconhold,
-	  registered by ast_bucket_scheme_register().
+	  Change-Id: I654ba0debe8351038d4433716434a09370f04c9d
 
-	  ASTERISK-29262 #close
+2021-11-16 16:34 +0000 [4a4f1a5c9a]  Mike Bradeen <mbradeen@sangoma.com>
 
-	  Change-Id: If0ea8697587353dce358a70035d82649fd4632b6
+	* astobj2.c: Fix core when ref_log enabled
 
-2020-12-22 04:42 +0000 [017e09b40a]  Robert Cripps <rcripps@voxbone.com>
+	  In the AO2_ALLOC_OPT_LOCK_NOLOCK case the referenced obj
+	  structure is freed, but is then referenced later if ref_log is
+	  enabled. The change is to store the obj->priv_data.options value
+	  locally and reference it instead of the value from the freed obj
 
-	* res/res_pjsip_session.c: Check that media type matches in
-	  function ast_sip_session_media_state_add.
+	  ASTERISK-29730
 
-	  Check ast_media_type matches when a ast_sip_session_media is found
-	  otherwise when transitioning from say image to audio, the wrong
-	  session is returned in the first if statement.
+	  Change-Id: I60cc5dc1f5a4330e7ad56976fc38a42de0ab6072
 
-	  ASTERISK-29220 #close
+2021-11-19 03:46 +0000 [726d6dd166]  Alexander Traud <pabstraud@compuserve.com>
 
-	  Change-Id: I6f6efa9b821ebe8881bb4c8c957f8802ddcb4b5d
+	* channels: Fix for Doxygen.
 
-2021-01-14 08:47 +0000 [fb42b60326]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK-29762
 
-	* res_pjsip_pubsub: Fix truncation of persisted SUBSCRIBE packet
+	  Change-Id: Ia8811ac12b93ff8c18164699c6fbc604cb0a23f7
 
-	  The last argument to ast_copy_string() is the buffer size, not the
-	  number of characters, so we add 1 to avoid stamping out the final \n
-	  in the persisted SUBSCRIBE message.
+2021-11-16 04:06 +0000 [3a4c9ec0e2]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  Change-Id: I019b78942836f57965299af15d173911fcead5b2
+	* bridge: Deny full Local channel pair in bridge.
 
-2021-01-08 10:02 +0000 [9c56870929]  Jaco Kroon <jaco@uls.co.za>
+	  Local channels are made up of two pairs - the 1 and 2
+	  sides. When a frame goes in one side, it comes out the
+	  other. Back and forth. When both halves are in a
+	  bridge this creates an infinite loop of frames.
 
-	* AC_HEADER_STDC causes a compile failure with autoconf 2.70
-
-	  From https://www.mail-archive.com/bug-autoconf@gnu.org/msg04408.html
+	  This change makes it so that bridging no longer
+	  allows both of these sides to exist in the same
+	  bridge.
 
-	  > ... the long-obsolete AC_HEADER_STDC, previously used internally by
-	  > AC_INCLUDES_DEFAULT, used AC_EGREP_HEADER.  The AC_HEADER_STDC macro
-	  > is now a no-op (and is not used at all within Autoconf anymore), so
-	  > that change is likely what made the first use of AC_EGREP_HEADER the
-	  > one inside the if condition, causing the observed results.
+	  ASTERISK-29748
 
-	  The implication is that the test does nothing anyway, and due to it
-	  being a no-op from 2.70 onwards, results in the required not being set
-	  to yes, resulting in ./configure to fail.
+	  Change-Id: I29928b6de87cd9be996a77daccefd7c360fef651
 
-	  Change-Id: Ic1ff38d87f791fbf1f2a80512f81bb7110392460
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+2021-11-06 18:35 +0000 [4468fc11d6]  Naveen Albert <asterisk@phreaknet.org>
 
-2021-01-15 03:33 +0000 [a25bcf70ed]  Alexander Traud <pabstraud@compuserve.com>
+	* res_tonedetect: Add call progress tone detection
 
-	* pjsip_scheduler: Fix pjsip show scheduled_tasks like for compiler Clang.
+	  Makes basic call progress tone detection available
+	  in a tech-agnostic manner with the addition of the
+	  ToneScan application. This can determine if the channel
+	  has encountered a busy signal, SIT tones, dial tone,
+	  modem, fax machine, etc. A few basic async progress
+	  tone detect options are also added to the TONE_DETECT
+	  function.
 
-	  Otherwise, Clang 10 warned because of logical-not-parentheses.
+	  ASTERISK-29720 #close
 
-	  Change-Id: Ia8fb493f727b08070eb2dcf520c08df34ed11d79
+	  Change-Id: Ia02437e0450473031e294798b8cb421fb8f24e90
 
-2021-01-15 05:09 +0000 [3f119192bb]  Alexander Traud <pabstraud@compuserve.com>
+2021-11-08 13:59 +0000 [f6aed7b8d1]  Boris P. Korzun <drtr0jan@yandex.ru>
 
-	* res_pjsip_session: Avoid sometimes-uninitialized warning with Clang.
+	* rtp_engine: Add type field for JSON RTCP Report stasis messages
 
-	  ASTERISK-29248
+	  ASTERISK-29727 #close
 
-	  Change-Id: I2b17bd5ffb246bc64c463402c9831413da78a556
+	  Change-Id: I2eca8aeb591cb63ac2238d08eab662367453cb82
 
-2021-01-11 14:25 +0000 [87a35f8e94]  Ben Ford <bford@digium.com>
+2021-11-17 03:24 +0000 [00fc7212bd]  Alexander Traud <pabstraud@compuserve.com>
 
-	* chan_pjsip.c: Add parameters to frame in indicate.
+	* odbc: Fix for Doxygen.
 
-	  There are a couple of parameters (datalen and data) that do not get set
-	  in chan_pjsip_indicate which could cause an Invalid message to pop up
-	  for things such as fax. This patch adds them to the frame.
+	  ASTERISK-29754
 
-	  Change-Id: Ia51be086a0708be905e73d1f433572c49c7e38f8
+	  Change-Id: Ia09eb68d283d201d9a6fbeccfc0efe83fe0502a5
 
-2021-01-14 16:26 +0000  Asterisk Development Team <asteriskteam@digium.com>
+2021-11-17 02:54 +0000 [241dbb1ec0]  Alexander Traud <pabstraud@compuserve.com>
 
-	* asterisk 18.2.0-rc1 Released.
+	* parking: Fix for Doxygen.
 
-2021-01-14 09:56 +0000 [89fea9bafe]  Asterisk Development Team <asteriskteam@digium.com>
+	  ASTERISK-29753
 
-	* Update CHANGES and UPGRADE.txt for 18.2.0
-2020-12-30 07:56 +0000 [c10557c401]  Jean Aunis <jean.aunis@prescom.fr>
+	  Change-Id: I7a61974584f6169502e6860fc711919fe7bbfaa7
 
-	* Stasis/messaging: tech subscriptions conflict with endpoint subscriptions.
+2021-11-17 06:18 +0000 [634e3ebdb8]  Alexander Traud <pabstraud@compuserve.com>
 
-	  When both a tech subscription and an endpoint subscription exist for a given
-	  endpoint, TextMessageReceived events are dispatched to the tech subscription
-	  only.
+	* res_ari: Fix for Doxygen.
 
-	  ASTERISK-29229
+	  ASTERISK-29756
 
-	  Change-Id: I9eac4cba5f9e27285a282509395347abc58fc2b8
+	  Change-Id: I2f1c1eea1c902492b77b74de9950f20ebbb7e758
 
-2020-12-29 12:16 +0000 [c3fad2fd01]  Ivan Poddubnyi <ivan.poddubny@gmail.com>
+2021-11-17 04:26 +0000 [c30ed45c94]  Alexander Traud <pabstraud@compuserve.com>
 
-	* chan_pjsip: Assign SIPDOMAIN after creating a channel
+	* frame: Fix for Doxygen.
 
-	  session->channel doesn't exist until chan_pjsip creates it, so intead of
-	  setting a channel variable every new incoming call sets one and the same
-	  global variable.
+	  ASTERISK-29755
 
-	  This patch moves the code to chan_pjsip so that SIPDOMAIN is set on
-	  a newly created channel, it also removes a misleading reference to
-	  channel->session used to fetch call pickup configuraion.
+	  Change-Id: I8240013ec3db0669c0acf67e26bf6c9cbb5b72af
 
-	  ASTERISK-29240
+2021-11-17 05:43 +0000 [9ae084ff44]  Alexander Traud <pabstraud@compuserve.com>
 
-	  Change-Id: I90c9bbbed01f5d8863585631a29322ae4e046755
+	* ari-stubs: Avoid 'is' as comparism with an literal.
 
-2020-12-23 08:44 +0000 [ad606d4ad1]  Alexander Traud <pabstraud@compuserve.com>
+	  Python 3.9.7 gave a syntax warning.
 
-	* chan_sip: SDP: Sidestep stream parsing when its media is disabled.
+	  Change-Id: I3e3a982fe720726bc0015bcdb0e638a626ec89d4
 
-	  Previously, chan_sip parsed all known media streams in an SDP offer
-	  like video (and text) even when videosupport=no (and textsupport=no).
-	  This wasted processor power. Furthermore, chan_sip accepted SDP offers,
-	  including no audio but just video (or text) streams although
-	  videosupport=no (or textsupport=no). Finally, chan_sip denied the whole
-	  offer instead of individual streams when they had encryption (SDES-sRTP)
-	  unexpectedly enabled.
+2021-11-08 09:42 +0000 [5d8e0a6542]  Alexander Traud <pabstraud@compuserve.com>
 
-	  ASTERISK-29238
-	  ASTERISK-29237
-	  ASTERISK-29222
+	* BuildSystem: Consistently allow 'ye' even for Jansson.
 
-	  Change-Id: Ie49e4e2a11f0265f914b684738348ba8c0f89755
+	  Furthermore, consistently use not 'No' but ':' for non-existent file
+	  paths. Finally, use the same pattern for checking file paths:
+	    a)  = ":"
+	    b) != "x:"
 
-2020-12-31 05:53 +0000 [cc496044db]  Ivan Poddubnyi <ivan.poddubny@gmail.com>
+	  Change-Id: I0c80c76d2cc98b0e5c859131290f4e3141a1a544
 
-	* chan_pjsip: Stop queueing control frames twice on outgoing channels
+2021-11-16 10:26 +0000 [acd1cd66b8]  Alexander Traud <pabstraud@compuserve.com>
 
-	  The fix for ASTERISK-27902 made chan_pjsip process SIP responses twice.
-	  This resulted in extra noise in logs (for example, "is making progress"
-	  and "is ringing" get logged twice by app_dial), as well as in noise in
-	  signalling: one incoming 183 Session Progress results in 2 outgoing 183-s.
+	* stasis: Fix for Doxygen.
 
-	  This change splits the response handler into 2 functions:
-	   - one for updating HANGUPCAUSE, which is still called twice,
-	   - another that does the rest, which is called only once as before.
+	  ASTERISK-29750
 
-	  ASTERISK-28016
-	  Reported-by: Alex Hermann
+	  Change-Id: Iea50173e785b2e9d49bc24c0af7111cfd96d44a9
 
-	  ASTERISK-28549
-	  Reported-by: Gant Liu
+2021-11-17 02:30 +0000 [173bc6b4c3]  Alexander Traud <pabstraud@compuserve.com>
 
-	  ASTERISK-28185
-	  Reported-by: Julien
+	* app: Fix for Doxygen.
 
-	  Change-Id: I0a1874be5bb5ed12d572d17c7f80de6e5e542940
+	  ASTERISK-29752
 
-2020-12-17 09:24 +0000 [cba8426b4c]  Mark Petersen <bugs.digium.com@zombie.dk>
+	  Change-Id: If40cbd01d47a6cfd620b18206dedb8460216c8af
 
-	* contrib/systemd: Added note on common issues with systemd and asterisk
+2021-11-16 06:51 +0000 [845ece8bc4]  Alexander Traud <pabstraud@compuserve.com>
 
-	  With newer version of linux /var/run/ is a symlink to /run/ that has
-	  been turned into tmpfs.
+	* res_xmpp: Fix for Doxygen.
 
-	  Added note that if asterisk has to bind to a specific IP that
-	  systemd has to wait until the network is up.
+	  ASTERISK-29749
 
-	  Added note on how to make sure that the environment variable
-	  HOSTNAME is included.
+	  Change-Id: I7885793b63bdeaa883e76edb899bbba9660eb1c5
 
-	  ASTERISK-29216
-	  Reported by: Mark Petersen
-	  Tested by: Mark Petersen
+2021-11-16 12:07 +0000 [fa91010229]  Alexander Traud <pabstraud@compuserve.com>
 
-	  Change-Id: Ib3e560655befd3e99eec743687144f5569533379
+	* channel: Fix for Doxygen.
 
-2021-01-07 08:40 +0000 [b3927ff8bc]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-29751
 
-	* Revert "res_pjsip_outbound_registration.c:  Use our own scheduler and other stuff"
+	  Change-Id: Ie04da5029c57ebee44733bdf05013156abe80176
 
-	  This reverts commit 860e40dd80f0603582b98a7da8150f15b564cce3.
+2021-11-13 06:04 +0000 [4051434be4]  Alexander Traud <pabstraud@compuserve.com>
 
-	  Reason for revert: Too many issues reported.  Need to research and correct.
+	* chan_iax2: Fix for Doxygen.
 
-	  ASTERISK-29230
-	  ASTERISK-29231
-	  Reported by: Michael Maier
+	  ASTERISK-29737
 
-	  Change-Id: I9011e2eecda4e91e1cfeeda6d1a7f1a0453eab41
+	  Change-Id: I282003cc553989fd5c19ceeac9e478fa4ee06cec
 
-2020-12-18 13:06 +0000 [3a230cc6a9]  Jaco Kroon <jaco@uls.co.za>
+2021-11-16 03:40 +0000 [463f6c83e8]  Alexander Traud <pabstraud@compuserve.com>
 
-	* func_lock: fix multiple-channel-grant problems.
+	* res_pjsip: Fix for Doxygen.
 
-	  Under contention it becomes possible that multiple channels will be told
-	  they successfully obtained the lock, which is a bug.  Please refer
+	  ASTERISK-29747
 
-	  ASTERISK-29217
+	  Change-Id: Ic7a1e9453f805a6264fe86c96b7d18b87b376084
 
-	  This introduces a couple of changes.
+2021-11-15 08:12 +0000 [8944dc78d1]  Alexander Traud <pabstraud@compuserve.com>
 
-	  1.  Replaces requesters ao2 container with simple counter (we don't
-	      really care who is waiting for the lock, only how many).  This is
-	      updated undex ->mutex to prevent memory access races.
-	  2.  Correct semantics for ast_cond_timedwait() as described in
-	      pthread_cond_broadcast(3P) is used (multiple threads can be released
-	      on a single _signal()).
-	  3.  Module unload races are taken care of and memory properly cleaned
-	      up.
+	* bridges: Fix for Doxygen.
 
-	  Change-Id: I6f68b5ec82ff25b2909daf6e4d19ca864a463e29
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+	  ASTERISK-29743
 
-2020-12-23 11:41 +0000 [49f625b8db]  Jaco Kroon <jaco@uls.co.za>
+	  Change-Id: I6e1bbbaa5875e19994a328ab40a5d429c6010e8b
 
-	* pbx_lua:  Add LUA_VERSIONS environment variable to ./configure.
+2021-11-15 07:38 +0000 [2024c2e476]  Alexander Traud <pabstraud@compuserve.com>
 
-	  On Gentoo it's possible to have multiple lua versions installed, all
-	  with a path of /usr, so it's not possible to use the current --with-lua
-	  option to determisticly pin to a specific version as is required by the
-	  Gentoo PMS standards.
+	* addons: Fix for Doxygen.
 
-	  This environment variable allows to lock to specific versions,
-	  unversioned check will be skipped if this variable is supplied.
+	  ASTERISK-29742
 
-	  Change-Id: I8c403eda05df25ee0193960262ce849c7d2fd088
-	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+	  Change-Id: Ie752cb9638ced1ebe3a55d710c6c18ef6bd0aafc
 
-2020-12-07 16:59 +0000 [fb23f98521]  Dan Cropp <dan@amtelco.com>
+2021-11-15 07:18 +0000 [196c24df22]  Alexander Traud <pabstraud@compuserve.com>
 
-	* chan_pjsip: Incorporate channel reference count into transfer_refer().
+	* apps: Fix for Doxygen.
 
-	  Add channel reference count for PJSIP REFER. The call could be terminated
-	  prior to the result of the transfer. In that scenario, when the SUBSCRIBE/NOTIFY
-	  occurred several minutes later, it would attempt to access a session which was
-	  no longer valid.  Terminate event subscription if pjsip_xfer_initiate() or
-	  pjsip_xfer_send_request() fails in transfer_refer().
+	  ASTERISK-29740
 
-	  ASTERISK-29201 #close
-	  Reported-by: Dan Cropp
+	  Change-Id: Icb6fbcfea0a5f1c82caa5001902b6a786adbf307
 
-	  Change-Id: I3fd92fd14b4e3844d3d7b0f60fe417a4df5f2435
+2021-11-15 07:29 +0000 [47ade30c6b]  Alexander Traud <pabstraud@compuserve.com>
 
-2020-12-23 13:06 +0000 [0e1ba9a778]  Kevin Harwell <kharwell@sangoma.com>
+	* tests: Fix for Doxygen.
 
-	* app_mixmonitor: cleanup datastore when monitor thread fails to launch
+	  ASTERISK-29741
 
-	  launch_monitor_thread is responsible for creating and initializing
-	  the mixmonitor, and dependent data structures. There was one off
-	  nominal path after the datastore gets created that triggers when
-	  the channel being monitored is hung up prior to monitor starting
-	  itself.
+	  Change-Id: I012d72b237bda2ef2d0f86307dfc6dc7add4b54b
 
-	  If this happened the monitor thread would not "launch", and the
-	  mixmonitor object and associated objects are freed, including the
-	  underlying datastore data object. However, the datastore itself was
-	  not removed from the channel, so when the channel eventually gets
-	  destroyed it tries to access the previously freed datastore data
-	  and crashes.
+2021-11-12 13:52 +0000 [2b90194d63]  Alexander Traud <pabstraud@compuserve.com>
 
-	  This patch removes and frees datastore object itself from the channel
-	  before freeing the mixmonitor object thus ensuring the channel does
-	  not call it when destroyed.
+	* progdocs: Avoid multiple use of section labels.
 
-	  ASTERISK-28947 #close
+	  ASTERISK-29735
 
-	  Change-Id: Id4f9e958956d62473ed5ff06c98ae3436e839ff8
+	  Change-Id: I56935e73f7bd1d4ae2721d11040f4835da64b810
 
-2020-12-24 09:03 +0000 [9ff548f1db]  Sean Bright <sean.bright@gmail.com>
+2021-11-12 13:17 +0000 [e79271cca4]  Alexander Traud <pabstraud@compuserve.com>
 
-	* app_voicemail: Prevent deadlocks when out of ODBC database connections
+	* progdocs: Use Doxygen \example correctly.
 
-	  ASTERISK-28992 #close
+	  ASTERISK-29734
 
-	  Change-Id: Ia7d608924036139ee2520b840d077762d02668d0
+	  Change-Id: I83b51e85cd71867645ab3a8a820f8fd1f065abd2
 
-2020-12-22 17:40 +0000 [d9aef0e6e5]  Kevin Harwell <kharwell@sangoma.com>
+2021-11-13 04:40 +0000 [55110339ec]  Alexander Traud <pabstraud@compuserve.com>
 
-	* pbx_realtime: wrong type stored on publish of ast_channel_snapshot_type
+	* bridge_channel: Fix for Doxygen.
 
-	  A prior patch segmented channel snapshots, and changed the underlying
-	  data object type associated with ast_channel_snapshot_type stasis
-	  messages. Prior to Asterisk 18 it was a type ast_channel_snapshot, but
-	  now it type ast_channel_snapshot_update.
+	  ASTERISK-29736
 
-	  When publishing ast_channel_snapshot_type in pbx_realtime the
-	  ast_channel_snapshot was being passed in as the message data
-	  object. When a handler, expecting a data object type of
-	  ast_channel_snapshot_update, dereferenced this value a crash
-	  would occur.
+	  Change-Id: Ia5370289e6526001a6b52754b533bcea1a9d7e5c
 
-	  This patch makes it so pbx_realtime now uses the expected type, and
-	  channel snapshot publish method when publishing.
+2021-11-12 12:41 +0000 [57fef28dc9]  Alexander Traud <pabstraud@compuserve.com>
 
-	  ASTERISK-29168 #close
+	* progdocs: Avoid 'name' with Doxygen \file.
 
-	  Change-Id: I9a2cfa0ec285169317f4b9146e4027da8a4fe896
+	  Fixes four misuses of the parameter 'name'. Additionally, for
+	  consistency and to avoid such an issue in future, those few other
+	  places, which used '\file name', were changed just to '\file'. Then,
+	  Doxygen uses the name of the current file.
 
-2020-12-18 09:16 +0000 [68d3d3af6f]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK-29733
 
-	* asterisk: Export additional manager functions
+	  Change-Id: I0c18b4c863c6988b138c77448057349a9ee7052d
 
-	  Rename check_manager_enabled() and check_webmanager_enabled() to begin
-	  with ast_ so that the symbols are automatically exported by the
-	  linker.
+2021-11-15 13:02 +0000 [ad67f6966e]  Naveen Albert <asterisk@phreaknet.org>
 
-	  ASTERISK~29184
+	* app_morsecode: Fix deadlock
 
-	  Change-Id: I85762b9a5d14500c15f6bad6507138c8858644c9
+	  Fixes a deadlock in app_morsecode caused by locking
+	  the channel twice when reading variables from the
+	  channel. The duplicate lock is simply removed.
 
-2020-12-19 11:54 +0000 [3c8598ffef]  Nick French <nickfrench@gmail.com>
+	  ASTERISK-29744 #close
 
-	* res_pjsip: Prevent segfault in UDP registration with flow transports
+	  Change-Id: I204000701f123361d7f85e0498fedc90243c75e4
 
-	  Segfault occurs during outbound UDP registration when all
-	  transport states are being iterated over. The transport object
-	  in the transport is accessed, but flow transports have a NULL
-	  transport object.
+2021-10-25 12:51 +0000 [2320a96349]  Naveen Albert <asterisk@phreaknet.org>
 
-	  Modify to not iterate over any flow transport
+	* app_read: Fix custom terminator functionality regression
 
-	  ASTERISK-29210 #close
+	  Currently, when the t option is specified with no arguments,
+	  the # character is still treated as a terminator, even though
+	  no character should be treated as a terminator.
 
-	  Change-Id: If28dc3a18bdcbd0a49598b09b7fe4404d45c996a
-
-2020-12-26 12:14 +0000 [3d379845e6]  Richard Mudgett <rmudgett@digium.com>
-
-	* chan_vpb.cc: Fix compile errors.
-
-	  Fix the usual compile problem when someone adds a new callback to struct
-	  ast_channel_tech.
-
-	  Change-Id: I9bdeb8a8cc65f03b2d6e4f2eb5809af47c906c32
-
-2020-12-26 11:42 +0000 [027f4e3a21]  Richard Mudgett <rmudgett@digium.com>
-
-	* res_pjsip_session.c: Fix compiler warnings.
-
-	  AST_VECTOR_SIZE() returns a size_t.  This is not always equivalent to an
-	  unsigned long on all machines.
-
-	  Change-Id: I0a4189a104e6e3a2e2273de06620eaef19df9338
-
-2020-12-13 06:03 +0000 [d8b7a6f599]  Sungtae Kim <pchero21@gmail.com>
+	  This is because a previous regression fix was modified to
+	  remove the use of NULL as a default altogether. However,
+	  NULL and an empty string actually refer to different
+	  arrangements and should be treated differently. NULL is the
+	  default terminator (#), while an empty string removes the
+	  terminator altogether. This is the behavior being used by
+	  the rest of the core.
 
-	* res_pjsip_session: Fixed NULL active media topology handle
+	  Additionally, since S_OR catches empty strings as well as
+	  NULL (not intended), this is changed to a ternary operator
+	  instead, which fixes the behavior.
 
-	  Added NULL pointer check to prevent Asterisk crash.
+	  ASTERISK-29705 #close
 
-	  ASTERISK-29215
+	  Change-Id: I9b6b72196dd04f5b1e0ab5aa1b0adf627725e086
 
-	  Change-Id: If07e50ea8d78cb610af9195fc13b5dca4bfcef95
+2021-10-24 13:38 +0000 [126de2839b]  Naveen Albert <asterisk@phreaknet.org>
 
-2020-12-22 02:58 +0000 [a7aea71e60]  Torrey Searle <tsearle@voxbone.com>
+	* res_pjsip_callerid: Fix OLI parsing
 
-	* res/res_pjsip_diversion: prevent crash on tel: uri in History-Info
+	  Fix parsing of ANI2/OLI information, since it was previously
+	  parsing the user, when it should have been parsing other_param.
 
-	  Add a check to see if the URI is a Tel URI and prevent crashing on
-	  trying to retrieve the reason parameter.
+	  Also improves the parsing by using pjproject native functions
+	  rather than trying to parse the parameters ourselves like
+	  chan_sip did. A previous attempt at this caused a crash, but
+	  this works correctly now.
 
-	  ASTERISK-29191
-	  ASTERISK-29219
+	  ASTERISK-29703 #close
 
-	  Change-Id: I0320aa205f22cda511d60a2edf2b037e8fd6cc37
+	  Change-Id: I8f3c79032d9ea1a21d16f8e11f22bd8d887738a1
 
-2020-12-11 13:27 +0000 [13682210e2]  Sean Bright <sean.bright@gmail.com>
+2021-10-30 20:04 +0000 [b4966c4f2a]  Josh Soref <jsoref@users.noreply.github.com>
 
-	* app_chanspy: Spyee information missing in ChanSpyStop AMI Event
+	* build_tools: Spelling fixes
 
-	  The documentation in the wiki says there should be spyee-channel
-	  information elements in the ChanSpyStop AMI event.
+	  Correct typos of the following word families:
 
-	      https://wiki.asterisk.org/wiki/x/Xc5uAg
+	  binutils
 
-	  However, this is not the case in Asterisk <= 16.10.0 Version. We're
-	  using these Spyee* arguments since Asterisk 11.x, so these arguments
-	  vanished in Asterisk 12 or higher.
+	  ASTERISK-29714
 
-	  For maximum compatibility, we still send the ChanSpyStop event even if
-	  we are not able to find any 'Spyee' information.
+	  Change-Id: I2f676ab48cd50edc400c43307cb53679e4c09b97
 
-	  ASTERISK-28883 #close
+2021-10-30 20:04 +0000 [815e99d5ea]  Josh Soref <jsoref@users.noreply.github.com>
 
-	  Change-Id: I81ce397a3fd614c094d043ffe5b1b1d76188835f
+	* contrib: Spelling fixes
 
-2020-11-30 19:27 +0000 [4b450b4334]  Sungtae Kim <pchero21@gmail.com>
+	  Correct typos of the following word families:
 
-	* res_ari: Fix wrong media uri handle for channel play
+	  standard
+	  increase
+	  comments
+	  valgrind
+	  promiscuous
+	  editing
+	  libtonezone
+	  storage
+	  aggressive
+	  whitespace
+	  russellbryant
+	  consecutive
+	  peternixon
 
-	  Fixed wrong null object handle in
-	  /channels/<channel_id>/play request handler.
+	  ASTERISK-29714
 
-	  ASTERISK-29188
+	  Change-Id: I9cafbf41b579c9c0c84c81719d2c4f900beec245
 
-	  Change-Id: I6691c640247a51ad15f23e4a203ca8430809bafe
+2021-10-30 20:04 +0000 [84556eb962]  Josh Soref <jsoref@users.noreply.github.com>
 
-2020-12-08 11:37 +0000 [7a6cfde4db]  Pirmin Walthert <infos@nappsoft.ch>
+	* codecs: Spelling fixes
 
-	* res_pjsip_nat.c: Create deep copies of strings when appropriate
+	  Correct typos of the following word families:
 
-	  In rewrite_uri asterisk was not making deep copies of strings when
-	  changing the uri. This was in some cases causing garbage in the route
-	  header and in other cases even crashing asterisk when receiving a
-	  message with a record-route header set. Thanks to Ralf Kubis for
-	  pointing out why this happens. A similar problem was found in
-	  res_pjsip_transport_websocket.c. Pjproject needs as well to be patched
-	  to avoid garbage in CANCEL messages.
+	  voiced
+	  denumerator
+	  codeword
+	  upsampling
+	  constructed
+	  residual
+	  subroutine
+	  conditional
+	  quantizing
+	  courtesy
+	  number
 
-	  ASTERISK-29024 #close
+	  ASTERISK-29714
 
-	  Change-Id: Ic5acd7fa2fbda3080f5f36ef12e46804939b198b
+	  Change-Id: I471fb8086a5277d8f05047fedee22cfa97a4252d
 
-2020-12-10 09:09 +0000 [ccb4951bf8]  George Joseph <gjoseph@digium.com>
+2021-10-30 20:04 +0000 [7285ba33ee]  Josh Soref <jsoref@users.noreply.github.com>
 
-	* logger.c: Automatically add a newline to formats that don't have one
+	* formats: Spelling fixes
 
-	  Scope tracing allows you to not specify a format string or
-	  variable, in which case it just prints the indent, file,
-	  function, and line number.  The trace output automatically
-	  adds a newline to the end in this case.  If you also have
-	  debugging turned on for the module, a debug message is
-	  also printed but the standard log functionality which
-	  prints it doesn't add the newline so you have messages
-	  that don't break correctly.
+	  Correct typos of the following word families:
 
-	   * format_log_message_ap(), which is the common log
-	     message formatter for all channels, now adds a
-	     newline to the end of format strings that don't
-	     already have a newline.
+	  truncate
 
-	  ASTERISK-29209
-	  Reported by: Alexander Traud
+	  ASTERISK-29714
 
-	  Change-Id: I994a7df27f88df343b7d19f3e81a4b562d9d41da
+	  Change-Id: I6507760c72b919873cff7cac22b3781036cd4955
 
-2020-12-16 06:17 +0000 [938a240793]  Joshua C. Colp <jcolp@sangoma.com>
+2021-10-30 20:04 +0000 [623fece76d]  Josh Soref <jsoref@users.noreply.github.com>
 
-	* res_pjsip_pidf_digium_body_supplement: Support Sangoma user agent.
+	* CREDITS: Spelling fixes
 
-	  This adds support for both Digium and Sangoma user agent strings
-	  for the Sangoma specific body supplement.
+	  Correct typos of the following word families:
 
-	  Change-Id: Ib99362b24b91d3cbe888d8b2fce3fad5515d9482
+	  contributors
 
-2020-12-10 17:06 +0000 [0774d9f9aa]  Nathan Bruning <nathan@iperity.com>
+	  ASTERISK-29714
 
-	* res_musiconhold: Don't crash when real-time doesn't return any entries
+	  Change-Id: I6f46dae8bf8125a21ce8ff318380b2b412d9d2f9
 
-	  ASTERISK-29211 #close
+2021-10-30 20:04 +0000 [01697d4836]  Josh Soref <jsoref@users.noreply.github.com>
 
-	  Change-Id: Ifbf0a4f786ab2a52342f9d1a1db4c9907f069877
+	* addons: Spelling fixes
 
-2020-10-29 12:21 +0000 [5b4e71fa0a]  Joshua C. Colp <jcolp@sangoma.com>
+	  Correct typos of the following word families:
 
-	* pjsip: Match lifetime of INVITE session to our session.
+	  definition
+	  listener
+	  fastcopy
+	  logical
+	  registration
+	  classify
+	  documentation
+	  explicitly
+	  dialed
+	  endpoint
+	  elements
+	  arithmetic
+	  might
+	  prepend
+	  byte
+	  terminal
+	  inquiry
+	  skipping
+	  aliases
+	  calling
+	  absent
+	  authentication
+	  transmit
+	  their
+	  ericsson
+	  disconnecting
+	  redir
+	  items
+	  client
+	  adapter
+	  transmitter
+	  existing
+	  satisfies
+	  pointer
+	  interval
+	  supplied
 
-	  In some circumstances it was possible for an INVITE
-	  session to be destroyed while we were still using it.
-	  This occurred due to the reference on the INVITE session
-	  being released internally as a result of its state
-	  changing to DISCONNECTED.
+	  ASTERISK-29714
 
-	  This change adds a reference to the INVITE session
-	  which is released when our own session is destroyed,
-	  ensuring that the INVITE session remains valid for
-	  the lifetime of our session.
+	  Change-Id: I8548438246f7b718d88e0b9e0a1eb384bbec88e4
 
-	  ASTERISK-29022
+2021-10-30 20:04 +0000 [b9e888418e]  Josh Soref <jsoref@users.noreply.github.com>
 
-	  Change-Id: I300c6d9005ff0e6efbe1132daefc7e47ca6228c9
+	* configs: Spelling fixes
 
-2020-10-29 06:25 +0000 [92fcd4edba]  laszlovl <digium@lvlconsultancy.nl>
+	  Correct typos of the following word families:
 
-	* Introduce astcachedir, to be used for temporary bucket files
+	  password
+	  excludes
+	  undesirable
+	  checksums
+	  through
+	  screening
+	  interpreting
+	  database
+	  causes
+	  initiation
+	  member
+	  busydetect
+	  defined
+	  severely
+	  throughput
+	  recognized
+	  counter
+	  require
+	  indefinitely
+	  accounts
 
-	  As described in the issue, /tmp is not a suitable location for a
-	  large amount of cached media files, since most distributions make
-	  /tmp a RAM-based tmpfs mount with limited capacity.
+	  ASTERISK-29714
 
-	  I opted for a location that can be configured separately, as opposed
-	  to using a subdirectory of spooldir, given the different storage
-	  profile (transient files vs files that might stay there indefinitely).
+	  Change-Id: Ie8f2a7b274a162dd627ee6a2165f5e8a3876527e
 
-	  This commit just makes the cache directory configurable, but leaves
-	  it at /tmp by default, to ensure backwards compatibility.
+2021-10-30 20:04 +0000 [de6ab15e6a]  Josh Soref <jsoref@users.noreply.github.com>
 
-	  A future commit that only targets master could change the default
-	  location to something more sensible such as /var/tmp/asterisk. At
-	  that point, the cachedir could be created and cleaned up during
-	  uninstall by the Makefile script.
+	* doc: Spelling fixes
 
-	  ASTERISK-29143
+	  Correct typos of the following word families:
 
-	  Change-Id: Ic54e95199405abacd9e509cef5f08fa14c510b5d
+	  transparent
+	  roughly
 
-2020-11-21 11:51 +0000 [f39d5ea7cd]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK-29714
 
-	* res_http_media_cache.c: Set reasonable number of redirects
+	  Change-Id: I2b90c68dfde4aa3f0d58f64f8187465336acb1b3
 
-	  By default libcurl does not follow redirects, so we explicitly enable
-	  it by setting CURLOPT_FOLLOWLOCATION. Once that is enabled, libcurl
-	  will follow up to CURLOPT_MAXREDIRS redirects, which by default is
-	  configured to be unlimited.
+2021-10-30 20:04 +0000 [33a5c32bf6]  Josh Soref <jsoref@users.noreply.github.com>
 
-	  This patch sets CURLOPT_MAXREDIRS to a more reasonable default (8). If
-	  we determine at some point that this needs to be increased on
-	  configurable it is a trivial change.
+	* menuselect: Spelling fixes
 
-	  ASTERISK-29173 #close
+	  Correct typos of the following word families:
 
-	  Change-Id: I4925ebbcf0c7d728bb9252b3795b3479ae225b30
+	  dependency
+	  unless
+	  random
+	  dependencies
+	  delimited
+	  randomly
+	  modules
 
-2020-11-23 14:56 +0000 [f9438e6457]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK-29714
 
-	* media_cache: Fix reference leak with bucket file metadata
+	  Change-Id: I3920603a8dc7c0a1852d2f885e06b1144692d40e
 
-	  Change-Id: Ia0e4124110df613ce5fdfa9ef8780016ebaa52c6
+2021-10-30 20:04 +0000 [5d3a115bee]  Josh Soref <jsoref@users.noreply.github.com>
 
-2020-11-24 00:55 +0000 [6a85dc860f]  Stanislav <stas.abramenkov@gmail.com>
+	* include: Spelling fixes
 
-	* res_pjsip_stir_shaken: Fix module description
+	  Correct typos of the following word families:
 
-	  the 'J' is missing in module description.
+	  activities
+	  forward
+	  occurs
+	  unprepared
+	  association
+	  compress
+	  extracted
+	  doubly
+	  callback
+	  prometheus
+	  underlying
+	  keyframe
+	  continue
+	  convenience
+	  calculates
+	  ignorepattern
+	  determine
+	  subscribers
+	  subsystem
+	  synthetic
+	  applies
+	  example
+	  manager
+	  established
+	  result
+	  microseconds
+	  occurrences
+	  unsuccessful
+	  accommodates
+	  related
+	  signifying
+	  unsubscribe
+	  greater
+	  fastforward
+	  itself
+	  unregistering
+	  using
+	  translator
+	  sorcery
+	  implementation
+	  serializers
+	  asynchronous
+	  unknowingly
+	  initialization
+	  determining
+	  category
+	  these
+	  persistent
+	  propagate
+	  outputted
+	  string
+	  allocated
+	  decremented
+	  second
+	  cacheability
+	  destructor
+	  impaired
+	  decrypted
+	  relies
+	  signaling
+	  based
+	  suspended
+	  retrieved
+	  functions
+	  search
+	  auth
+	  considered
+
+	  ASTERISK-29714
+
+	  Change-Id: I542ce887a16603f886a915920d5710d4a0a1358d
+
+2021-10-30 20:04 +0000 [83a2e76671]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* UPGRADE.txt: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  themselves
+	  support
+	  received
+
+	  ASTERISK-29714
+
+	  Change-Id: Ibd0a7996d5801c754d3d44fba31fe788a13dba95
+
+2021-10-30 20:04 +0000 [2a8fb4695e]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* bridges: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  multiplication
+	  potentially
+	  iteration
+	  interaction
+	  virtual
+	  synthesis
+	  convolve
+	  initializes
+	  overlap
+
+	  ASTERISK-29714
+
+	  Change-Id: Ia40f1aca8f2996ab407c6ed9d24cb10a67c6684b
+
+2021-10-30 20:04 +0000 [eb03b18ff9]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* apps: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  simultaneously
+	  administrator
+	  directforward
+	  attachfmt
+	  dailplan
+	  automatically
+	  applicable
+	  nouns
+	  explicit
+	  outside
+	  sponsored
+	  attachment
+	  audio
+	  spied
+	  doesn't
+	  counting
+	  encoded
+	  implements
+	  recursively
+	  emailaddress
+	  arguments
+	  queuerules
+	  members
+	  priority
+	  output
+	  advanced
+	  silencethreshold
+	  brazilian
+	  debugging
+	  argument
+	  meadmin
+	  formatting
+	  integrated
+	  sneakiness
+
+	  ASTERISK-29714
+
+	  Change-Id: Ie5ecaec91c00b26309da4e51cfc0991a5bb7d092
+
+2021-10-30 20:04 +0000 [d46ba42910]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* channels: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  appease
+	  permanently
+	  overriding
+	  residue
+	  silliness
+	  extension
+	  channels
+	  globally
+	  reference
+	  japanese
+	  group
+	  coordinate
+	  registry
+	  information
+	  inconvenience
+	  attempts
+	  cadence
+	  payloads
+	  presence
+	  provisioning
+	  mimics
+	  behavior
+	  width
+	  natively
+	  syslabel
+	  not owning
+	  unquelch
+	  mostly
+	  constants
+	  interesting
+	  active
+	  unequipped
+	  brodmann
+	  commanding
+	  backlogged
+	  without
+	  bitstream
+	  firmware
+	  maintain
+	  exclusive
+	  practically
+	  structs
+	  appearance
+	  range
+	  retransmission
+	  indication
+	  provisional
+	  associating
+	  always
+	  whether
+	  cyrillic
+	  distinctive
+	  components
+	  reinitialized
+	  initialized
+	  capability
+	  switches
+	  occurring
+	  happened
+	  outbound
+
+	  ASTERISK-29714
+
+	  Change-Id: Ife52ee89cd2170b684fa651ca72b1cb911a57339
+
+2021-10-30 20:04 +0000 [e54a9d31f1]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* tests: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  mounting
+	  jitterbuffer
+	  thrashing
+	  original
+	  manipulating
+	  entries
+	  actual
+	  possibility
+	  tasks
+	  options
+	  positives
+	  taskprocessor
+	  other
+	  dynamic
+	  declarative
+
+	  ASTERISK-29714
+
+	  Change-Id: I6b94659d045eec5d8d020fce2e9b6e2f593dfeb6
+
+2021-10-30 20:04 +0000 [3bf314d643]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* CHANGES: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  issuing
+	  execution
+	  bridging
+	  alert
+	  respective
+	  unlikely
+	  confbridge
+	  offered
+	  negotiation
+	  announced
+	  engineer
+	  systems
+	  inherited
+	  passthrough
+	  functionality
+	  supporting
+	  conflicts
+	  semantically
+	  monitor
+	  specify
+	  specifiable
+
+	  ASTERISK-29714
+
+	  Change-Id: Ia6b1cf634f52c5f7b1b8769dc54dae78106ed98c
+
+2021-10-30 20:04 +0000 [1b1f5f9f67]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* funcs: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  effectively
+	  emitted
+	  expect
+	  anthony
+
+	  ASTERISK-29714
+
+	  Change-Id: Ic16f9ec855bb6d14ec8e170b90af9a36b06d488a
+
+2021-10-30 20:04 +0000 [ccb8b8ffbf]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* pbx: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  process
+	  populate
+	  with
+	  africa
+	  accessing
+	  contexts
+	  exercise
+	  university
+	  organizations
+	  withhold
+	  maintaining
+	  independent
+	  rotation
+	  ignore
+	  eventname
+
+	  ASTERISK-29714
+
+	  Change-Id: I90eacc5bc3dcf75a9c898cfb85164f37dec08345
+
+2021-10-30 20:04 +0000 [f382775241]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* main: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  analysis
+	  nuisance
+	  converting
+	  although
+	  transaction
+	  desctitle
+	  acquire
+	  update
+	  evaluate
+	  thousand
+	  this
+	  dissolved
+	  management
+	  integrity
+	  reconstructed
+	  decrement
+	  further on
+	  irrelevant
+	  currently
+	  constancy
+	  anyway
+	  unconstrained
+	  featuregroups
+	  right
+	  larger
+	  evaluated
+	  encumbered
+	  languages
+	  digits
+	  authoritative
+	  framing
+	  blindxfer
+	  tolerate
+	  traverser
+	  exclamation
+	  perform
+	  permissions
+	  rearrangement
+	  performing
+	  processing
+	  declension
+	  happily
+	  duplicate
+	  compound
+	  hundred
+	  returns
+	  elicit
+	  allocate
+	  actually
+	  paths
+	  inheritance
+	  atxferdropcall
+	  earlier
+	  synchronization
+	  multiplier
+	  acknowledge
+	  across
+	  against
+	  thousands
+	  joyous
+	  manipulators
+	  guaranteed
+	  emulating
+	  soundfile
+
+	  ASTERISK-29714
+
+	  Change-Id: I926ba4b11e9f6dd3fdd93170ab1f9b997910be70
+
+2021-10-30 20:04 +0000 [15c4814f55]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* utils: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  command-line
+	  immediately
+	  extensions
+	  momentarily
+	  mustn't
+	  numbered
+	  bytes
+	  caching
+
+	  ASTERISK-29714
+
+	  Change-Id: I8b2b125c5d4d2f9e87a58515c97468ad47ca44f8
+
+2021-10-30 20:04 +0000 [4490f0b962]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* Makefile: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  libraries
+	  install
+	  overwrite
+
+	  ASTERISK-29714
+
+	  Change-Id: I6488814f79186d6c23dfd7b7f9bba0a046126174
+
+2021-10-30 20:04 +0000 [9ae9893c63]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* res: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  identifying
+	  structures
+	  actcount
+	  initializer
+	  attributes
+	  statement
+	  enough
+	  locking
+	  declaration
+	  userevent
+	  provides
+	  unregister
+	  session
+	  execute
+	  searches
+	  verification
+	  suppressed
+	  prepared
+	  passwords
+	  recipients
+	  event
+	  because
+	  brief
+	  unidentified
+	  redundancy
+	  character
+	  the
+	  module
+	  reload
+	  operation
+	  backslashes
+	  accurate
+	  incorrect
+	  collision
+	  initializing
+	  instance
+	  interpreted
+	  buddies
+	  omitted
+	  manually
+	  requires
+	  queries
+	  generator
+	  scheduler
+	  configuration has
+	  owner
+	  resource
+	  performed
+	  masquerade
+	  apparently
+	  routable
+
+	  ASTERISK-29714
+
+	  Change-Id: I88485116d2c59b776aa2e1f8b4ce8239a21decda
+
+2021-10-30 20:04 +0000 [ff11d74331]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* rest-api-templates: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  overwritten
+	  descendants
+
+	  ASTERISK-29714
+
+	  Change-Id: I2307e35887a3437e50317a4b86f0893f25f9fd3b
+
+2021-10-30 20:04 +0000 [9641d15039]  Josh Soref <jsoref@users.noreply.github.com>
+
+	* agi: Spelling fixes
+
+	  Correct typos of the following word families:
+
+	  pretend
+	  speech
+
+	  ASTERISK-29714
+
+	  Change-Id: I7d0527c329cda07552247ea11b2d7db207a3d87d
+
+2021-11-08 07:00 +0000 [f1f23bbe4e]  George Joseph <gjoseph@digium.com>
+
+	* CI: Rename 'master' node to 'built-in'
+
+	  Jenkins renamed the 'master' node to 'built-in' in version
+	  2.319 so we have to adjust as well.
+
+	  Change-Id: Ice663c3a66d0eedf76e8e5fe530328455991ec25
+
+2021-11-08 08:08 +0000 [b8db1daec6]  Alexander Traud <pabstraud@compuserve.com>
+
+	* BuildSystem: In POSIX sh, == in place of = is undefined.
+
+	  ASTERISK-29724
+
+	  Change-Id: I59aa0e52effdc16992f3a736ccf73430a6ef135b
+
+2021-11-08 09:01 +0000 [a109b5aee0]  Sean Bright <sean.bright@gmail.com>
+
+	* pbx.c: Don't remove dashes from hints on reload.
+
+	  When reloading dialplan, hints created dynamically would lose any dash
+	  characters. Now we ignore those dashes if we are dealing with a hint
+	  during a reload.
+
+	  ASTERISK-28040 #close
+
+	  Change-Id: I95e48f5a268efa3c6840ab69798525d3dce91636
+
+2021-10-24 06:55 +0000 [f9ba1ee7c9]  Naveen Albert <asterisk@phreaknet.org>
+
+	* sig_analog: Fix truncated buffer copy
+
+	  Fixes compiler warning caused by a truncated copy of the ANI2 into a
+	  buffer of size 10. This could prevent the null terminator from being
+	  copied if the copy value exceeds the size of the buffer. This increases
+	  the buffer size to 101 to ensure there is no way for truncation to occur.
+
+	  ASTERISK-29702 #close
+
+	  Change-Id: Ief9052212952840fa44de6463b8699fdb3e163d0
+
+2021-10-24 07:31 +0000 [4e514419d9]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_voicemail: Fix phantom voicemail bug on rerecord
+
+	  If users are able to press # for options while leaving
+	  a message and then press 3 to rerecord the message, if
+	  the caller hangs up during the rerecord prompt but before
+	  Asterisk starts recording a message, then an "empty"
+	  voicemail gets processed whereby an email gets sent out
+	  notifying the user of a 0:00 duration message. The file
+	  doesn't actually exist, so playback will fail since there
+	  was no message to begin with.
+
+	  This adds a check after the streaming of the rerecord
+	  announcement to see if the caller has hung up. If so,
+	  we bail out early so that we can clean up properly.
+
+	  ASTERISK-29391 #close
+
+	  Change-Id: Id965d72759a2fd3b39afb76fec08aaebebe75c31
+
+2021-10-25 19:47 +0000 [df9aeea4c8]  Naveen Albert <asterisk@phreaknet.org>
+
+	* chan_iax2: Allow both secret and outkey at dial time
+
+	  Historically, the dial syntax for IAX2 has held that
+	  an outkey (used only for RSA authenticated calls)
+	  and a secret (used only for plain text and MD5 authenticated
+	  calls, historically) were mutually exclusive, and thus
+	  the same position in the dial string was used for both
+	  values.
+
+	  Now that encryption is possible with RSA authentication,
+	  this poses a limitation, since encryption requires a
+	  secret and RSA authentication requires an outkey. Thus,
+	  the dial syntax is extended so that both a secret and
+	  an outkey can be specified.
+
+	  The new extended syntax is backwards compatible with the
+	  old syntax. However, a secret can now be specified after
+	  the outkey, or the outkey can be specified after the secret.
+	  This makes it possible to spawn an encrypted RSA authenticated
+	  call without a corresponding peer being predefined in iax.conf.
+
+	  ASTERISK-29707 #close
+
+	  Change-Id: I1f8149313ed760169d604afbb07720a8b07dd00e
+
+2021-10-28 07:09 +0000 [d1163653d1]  Alexander Traud <pabstraud@compuserve.com>
+
+	* res_snmp: As build tool, prefer pkg-config over net-snmp-config.
+
+	  ASTERISK-29709
+
+	  Change-Id: Ie169df878bdfc3a06b3097c5c38d185b480f54d4
+
+2021-11-04 05:22 +0000 [ee0ed3ae49]  Alexander Traud <pabstraud@compuserve.com>
+
+	* res_config_sqlite: Remove deprecated module.
+
+	  ASTERISK-29717
+
+	  Change-Id: I64b914eef744542528f7d4396bd06715898fbc55
+
+2021-10-28 07:41 +0000 [14709ae12d]  Alexander Traud <pabstraud@compuserve.com>
+
+	* stasis: Avoid 'dispatched' as unused variable in normal mode.
+
+	  ASTERISK-29710
+
+	  Change-Id: Ia849f1172e4e694c5d5d7f0cad449f936ee12216
+
+2021-10-29 10:05 +0000 [ce2d743d59]  Sean Bright <sean@seanbright.com>
+
+	* various: Fix GCC 11.2 compilation issues.
+
+	  * Initialize some variables that are never used anyway.
+
+	  * Use valid pointers instead of integers cast to void pointers when
+	    calling pthread_setspecific().
+
+	  ASTERISK-29711 #close
+	  ASTERISK-29713 #close
+
+	  Change-Id: I8728cd6f2f4b28e0e48113c5da450b768c2a6683
+
+2021-09-09 09:39 +0000 [8aea2e5929]  George Joseph <gjoseph@digium.com>
+
+	* ast_coredumper:  Refactor to better find things
+
+	  The search for a running asterisk when --running is used
+	  has been greatly simplified and in the event it doesn't
+	  work, you can now specify a pid to use on the command
+	  line with --pid.
+
+	  The search for asterisk modules when --tarball-coredumps
+	  is used has been enhanced to have a better chance of finding
+	  them and in the event it doesn't work, you can now specify
+	  --libdir on the command line to indicate the library directory
+	  where they were installed.
+
+	  The DATEFORMAT variable was renamed to DATEOPTS and is now
+	  passed to the 'date' utility rather than running DATEFORMAT
+	  as a command.
+
+	  The coredump and output files are now renamed with DATEOPTS.
+	  This can be disabled by specifying --no-rename.
+
+	  Several confusing and conflicting options were removed:
+	  --append-coredumps
+	  --conffile
+	  --no-default-search
+	  --tarball-uniqueid
+
+	  The script was re-structured to make it easier for follow.
+
+	  Change-Id: I674be64bdde3ef310b6a551d4911c3b600ffee59
+
+2021-10-21 12:29 +0000 [67d1f881eb]  Kevin Harwell <kharwell@sangoma.com>
+
+	* strings/json: Add string delimter match, and object create with vars methods
+
+	  Add a function to check if there is an exact match a one string between
+	  delimiters in another string.
+
+	  Add a function that will create an ast_json object out of a list of
+	  Asterisk variables. An excludes string can also optionally be passed
+	  in.
+
+	  Also, add a macro to make it easier to get object integers.
+
+	  Change-Id: I5f34f18e102126aef3997f19a553a266d70d6226
+
+2021-09-21 12:09 +0000 [1031a1805b]  Ben Ford <bford@digium.com>
+
+	* STIR/SHAKEN: Option split and response codes.
+
+	  The stir_shaken configuration option now has 4 different choices to pick
+	  from: off, attest, verify, and on. Off and on behave the same way they
+	  do now. Attest will only perform attestation on the endpoint, and verify
+	  will only perform verification on the endpoint.
+
+	  Certain responses are required to be sent based on certain conditions
+	  for STIR/SHAKEN. For example, if we get a Date header that is outside of
+	  the time range that is considered valid, a 403 Stale Date response
+	  should be sent. This and several other responses have been added.
+
+	  Change-Id: I4ac1ecf652cd0e336006b0ca638dc826b5b1ebf7
+
+2021-08-25 08:15 +0000 [56ecf7005b]  Rodrigo Ramírez Norambuena <a@rodrigoramirez.com>
+
+	* app_queue: Add LoginTime field for member in a queue.
+
+	  Add a time_t logintime to storage a time when a member is added into a
+	  queue.
+
+	  Also, includes show this time (in seconds) using a 'queue show' command
+	  and the field LoginTime for response for AMI events.
+
+	  ASTERISK-18069 #close
+
+	  Change-Id: Ied6c3a300f78d78eebedeb3e16a1520fc3fff190
+
+2021-10-21 12:49 +0000 [8beac820c0]  Kevin Harwell <kharwell@sangoma.com>
+
+	* res_speech: Add a type conversion, and new engine unregister methods
+
+	  Add a new function that converts a speech results type to a string.
+	  Also add another function to unregister an engine, but returns a
+	  pointer to the unregistered engine object instead of a success/fail
+	  integer.
+
+	  Change-Id: I0f7de17cb411021c09fb03988bc2b904e1380192
+
+2021-10-07 13:07 +0000 [99a1a427a9]  Mike Bradeen <mbradeen@sangoma.com>
+
+	* various: Fix GCC 11 compilation issues.
+
+	  test_voicemail_api: Use empty char* for empty_msg_ids.
+	  chan_skinny: Fix size of calledParty to be maximum extension.
+	  menuselect: Change Makefile to stop deprecated warnings. Added comments
+	  test_linkedlist: 'bogus' variable was manually allocated from a macro
+	  and the test fails if this happens but the compiler couldn't 'see' this
+	  and returns a warning. memset to all 0's after allocation.
+	  chan_ooh323: Fixed various indentation issues that triggered misleading
+	   indentation warnings.
+
+	  ASTERISK-29682
+	  Reported by: George Joseph
+
+	  Change-Id: If4fe42222c8444dc16828a42731ee53b4ce5cbbe
+
+2021-09-20 11:10 +0000 [cfae5224e3]  Shloime Rosenblum <shloimerosenblum@gmail.com>
+
+	* apps/app_playback.c: Add 'mix' option to app_playback
+
+	  I am adding a mix option that will play by filename and say.conf unlike
+	  say option that will only play with say.conf. It
+	  will look on the format of the name, if it is like say it play with
+	  say.conf if not it will play the file name.
+
+	  ASTERISK-29662
+
+	  Change-Id: I815816916a308f0fa8f165140dc15772dcbd547a
+
+2021-10-19 11:35 +0000 [0adcdbd118]  George Joseph <gjoseph@digium.com>
+
+	* BuildSystem: Check for alternate openssl packages
+
+	  OpenSSL is one of those packages that often have alternatives
+	  with later versions.  For instance, CentOS/EL 7 has an
+	  openssl package at version 1.0.2 but there's an openssl11
+	  package from the epel repository that has 1.1.1.  This gets
+	  installed to /usr/include/openssl11 and /usr/lib64/openssl11.
+	  Unfortunately, the existing --with-ssl and --with-crypto
+	  ./configure options expect to point to a source tree and
+	  don't work in this situation.  Also unfortunately, the
+	  checks in ./configure don't use pkg-config.
+
+	  In order to make this work with the existing situation, you'd
+	  have to run...
+	  ./configure --with-ssl=/usr/lib64/openssl11 \
+	      --with-crypto=/usr/lib64/openssl11 \
+	      CFLAGS=-I/usr/include/openssl11
+
+	  BUT...  those options don't get passed down to bundled pjproject
+	  so when you run make, you have to include the CFLAGS again
+	  which is a big pain.
+
+	  Oh...  To make matters worse, although you can specify
+	  PJPROJECT_CONFIGURE_OPTS on the ./configure command line,
+	  they don't get saved so if you do a make clean, which will
+	  force a re-configure of bundled pjproject, those options
+	  don't get used.
+
+	  So...
+
+	  * In configure.ac... Since pkg-config is installed by install_prereq
+	    anyway, we now use it to check for the system openssl >= 1.1.0.
+	    If that works, great.  If not, we check for the openssl11
+	    package. If that works, great.  If not, we fall back to just
+	    checking for any openssl.  If pkg-config isn't installed for some
+	    reason, or --with-ssl=<dir> or --with-crypto=<dir> were specified
+	    on the ./configure command line, we fall back to the existing
+	    logic that uses AST_EXT_LIB_CHECK().
+
+	  * The whole OpenSSL check process has been moved up before
+	    THIRD_PARTY_CONFIGURE(), which does the initial pjproject
+	    bundled configure, is run.  This way the results of the above
+	    checks, which may result in new include or library directories,
+	    is included.
+
+	  * Although not strictly needed for openssl, We now save the value of
+	    PJPROJECT_CONFIGURE_OPTS in the makeopts file so it can be used
+	    again if a re-configure is triggered.
+
+	  ASTERISK-29693
+
+	  Change-Id: I341ab7603e6b156aa15a66f43675ac5029d5fbde
+
+2021-10-14 14:38 +0000 [886983b114]  Sean Bright <sean.bright@gmail.com>
+
+	* func_talkdetect.c: Fix logical errors in silence detection.
+
+	  There are 3 separate changes here:
+
+	  1. The documentation erroneously stated that the dsp_talking_threshold
+	     argument was a number of milliseconds when it is actually an energy
+	     level used by the DSP code to classify talking vs. silence.
+
+	  2. Fixes a copy paste error in the argument handling code.
+
+	  3. Don't erroneously switch to the talking state if we aren't actively
+	     handling a frame we've classified as talking.
+
+	  Patch inspired by one provided by Moritz Fain (License #6961).
+
+	  ASTERISK-27816 #close
+
+	  Change-Id: I5953fd570b98b49c41cee55bfe3b941753fb2511
+
+2021-10-11 14:04 +0000 [44fd75fae2]  Sean Bright <sean.bright@gmail.com>
+
+	* configure: Remove unused OpenSSL SRTP check.
+
+	  Discovered while looking at ASTERISK~29684. Usage was removed in change
+	  I3c77c7b00b2ffa2e935632097fa057b9fdf480c0.
+
+	  Change-Id: Iaf2f7a16ea5a7eee6375319347e4b40b8e7b10e3
+
+2021-10-12 13:17 +0000 [072f2ebb12]  Mike Bradeen <mbradeen@sangoma.com>
+
+	* build: prevent binary downloads for non x86 architectures
+
+	  download_externals: Add check for i686 and i386 (in addition
+	  to the current x86_64) and exit if not one of the three.
+
+	  ASTERISK-26497
+
+	  Change-Id: Ia4d429fcefa5b2f5b6e99159d4607de8e8325b2f
+
+2021-10-14 10:15 +0000 [51859252f7]  Sebastien Duthil <sduthil@wazo.community>
+
+	* main/stun.c: fix crash upon STUN request timeout
+
+	  Some ast_stun_request users do not provide a destination address when
+	  sending to a connection-mode socket.
+
+	  ASTERISK-29691
+
+	  Change-Id: Idd9114c3380216ba48abfc3c68619e79ad37defc
+
+2021-10-07 12:50 +0000 [9fcd50a8c9]  Sean Bright <sean.bright@gmail.com>
+
+	* Makefile: Use basename in a POSIX-compliant way.
+
+	  If you aren't using GNU coreutils, chances are that your basename
+	  doesn't know about the -s argument. Luckily for us, basename does what
+	  we need it do even without the -s argument.
+
+	  Change-Id: I8b81a429bb037b997ee6640ff8a2b5e860962bb7
+
+2021-10-05 19:59 +0000 [7fc26e8617]  Mark Murawski <markm@intellasoft.net>
+
+	* pbx_ael:  Fix crash and lockup issue regarding 'ael reload'
+
+	  Avoid infinite recursion and crash
+
+	  Change-Id: I8ed05ec3aa2806c50c77edc5dd0cd4e4fa08b3f4
+
+2021-05-24 13:04 +0000 [7ff6c43760]  Naveen Albert <asterisk@phreaknet.org>
+
+	* chan_iax2: Add encryption for RSA authentication
+
+	  Adds support for encryption to RSA-authenticated
+	  calls. Also prevents crashes if an RSA IAX2 call
+	  is initiated to a switch requiring encryption
+	  but no secret is provided.
+
+	  ASTERISK-20219
+
+	  Change-Id: I18f1f9d7c59b4f9cffa00f3b94a4c875846efd40
+
+2021-07-19 11:34 +0000 [5e9799a42e]  Matthew Kern <mkern@alconconstruction.com>
+
+	* res_pjsip_t38: bind UDPTL sessions like RTP
+
+	  In res_pjsip_sdp_rtp, the bind_rtp_to_media_address option and the
+	  fallback use of the transport's bind address solve problems sending
+	  media on systems that cannot send ipv4 packets on ipv6 sockets, and
+	  certain other situations. This change extends both of these behaviors
+	  to UDPTL sessions as well in res_pjsip_t38, to fix fax-specific
+	  problems on these systems, introducing a new option
+	  endpoint/t38_bind_udptl_to_media_address.
+
+	  ASTERISK-29402
+
+	  Change-Id: I87220c0e9cdd2fe9d156846cb906debe08c63557
+
+2021-09-29 12:58 +0000 [b40ca38c56]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_read: Fix null pointer crash
+
+	  If the terminator character is not explicitly specified
+	  and an indications tone is used for reading a digit,
+	  there is no null pointer check so Asterisk crashes.
+	  This prevents null usage from occuring.
+
+	  ASTERISK-29673 #close
+
+	  Change-Id: Ie941833e123c3dbfb88371b5de5edbbe065514ac
+
+2021-09-29 04:32 +0000 [6bc747b639]  Jean Aunis <jean.aunis@prescom.fr>
+
+	* res_rtp_asterisk: fix memory leak
+
+	  Add missing reference decrement in rtp_deallocate_transport()
+
+	  ASTERISK-29671
+
+	  Change-Id: I8d22dbedb90e8dade0829b7a28372f404b07caa9
+
+2021-09-19 15:08 +0000 [d20587250e]  Shloime Rosenblum <shloimerosenblum@gmail.com>
+
+	* main/say.c: Support future dates with Q and q format params
+
+	  The current versions do not support future dates in all say application when using the 'Q' or 'q' format parameter and says "today" for everything that is greater than today
+
+	  ASTERISK-29637
+
+	  Change-Id: I1fb1cef0ce3c18d87b1fc94ea309d13bc344af02
+
+2021-07-21 16:36 +0000 [47cb177baf]  Joseph Nadiv <ynadiv@corpit.xyz>
+
+	* res_pjsip_registrar: Remove unavailable contacts if exceeds max_contacts
+
+	  The behavior of max_contacts and remove_existing are connected.  If
+	  remove_existing is enabled, the soonest expiring contacts are removed.
+	  This may occur when there is an unavailable contact.  Similarly,
+	  when remove_existing is not enabled, registrations from good
+	  endpoints are rejected in favor of retaining unavailable contacts.
+
+	  This commit adds a new AOR option remove_unavailable, and the effect
+	  of this setting will depend on remove_existing.  If remove_existing
+	  is set to no, we will still remove unavailable contacts when they
+	  exceed max_contacts, if there are any. If remove_existing is set to
+	  yes, we will prioritize the removal of unavailable contacts before
+	  those that are expiring soonest.
+
+	  ASTERISK-29525
+
+	  Change-Id: Ia2711b08f2b4d1177411b1be23e970d7fdff5784
+
+2021-09-23 09:13 +0000 [0aac38c0ac]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* ari: Ignore invisible bridges when listing bridges.
+
+	  When listing bridges we go through the ones present in
+	  ARI, get their snapshot, turn it into JSON, and add it
+	  to the payload we ultimately return.
+
+	  An invisible "dial bridge" exists within ARI that would
+	  also try to be added to this payload if the channel
+	  "create" and "dial" routes were used. This would ultimately
+	  fail due to invisible bridges having no snapshot
+	  resulting in the listing of bridges failing.
+
+	  This change makes it so that the listing of bridges
+	  ignores invisible ones.
+
+	  ASTERISK-29668
+
+	  Change-Id: I14fa4b589b4657d1c2a5226b0f527f45a0cd370a
+
+2021-09-19 06:14 +0000 [d900130021]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_vmcount: Add support for multiple mailboxes
+
+	  Allows multiple mailboxes to be specified for VMCOUNT
+	  instead of just one.
+
+	  ASTERISK-29661 #close
+
+	  Change-Id: I9108528300795fd5b607efa9d4dd7b74be031813
+
+2021-09-21 09:58 +0000 [5ca9898dfb]  Sean Bright <sean.bright@gmail.com>
+
+	* message.c: Support 'To' header override with AMI's MessageSend.
+
+	  The MessageSend AMI action has been updated to allow the Destination
+	  and the To addresses to be provided separately. This brings the
+	  MessageSend manager command in line with the capabilities of the
+	  MessageSend dialplan application.
+
+	  ASTERISK-29663 #close
+
+	  Change-Id: I8513168d3e189a9fed88aaab6f5547ccb50d332c
+
+2021-09-15 13:21 +0000 [de6ecd5e34]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_channel: Add CHANNEL_EXISTS function.
+
+	  Adds a function to check for the existence of a channel by
+	  name or by UNIQUEID.
+
+	  ASTERISK-29656 #close
+
+	  Change-Id: Ib464e9eb6e13dc683a846286798fecff4fd943cb
+
+2021-09-05 13:11 +0000 [5abf499d23]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_queue: Fix hint updates for included contexts
+
+	  Previously, if custom hints were used with the hint:
+	  format in app_queue, when device state changes occured,
+	  app_queue would only do a literal string comparison of
+	  the context used for the hint in app_queue and the context
+	  of the hint which just changed state. This caused hints
+	  to not update and become stale if the context associated
+	  with the agent included the context which actually changes
+	  state, essentially completely breaking device state for
+	  any such agents defined in this manner.
+
+	  This fix adds an additional check to ensure that included
+	  contexts are also compared against the context which changed
+	  state, so that the behavior is correct no matter whether the
+	  context is specified to app_queue directly or indirectly.
+
+	  ASTERISK-29578 #close
+
+	  Change-Id: I8caf2f8da8157ef3d9ea71a8568c1eec95592b78
+
+2021-09-10 09:40 +0000 [02f54e2751]  Sean Bright <sean.bright@gmail.com>
+
+	* res_http_media_cache.c: Compare unaltered MIME types.
+
+	  Rather than stripping parameters from Content-Type headers before
+	  comparison, first try to compare the whole string. If no match is
+	  found, strip the parameters and try that way.
+
+	  ASTERISK-29275 #close
+
+	  Change-Id: I2963c8ecbb3a9605b78b6421c415108d77a66a0f
+
+2021-07-25 17:19 +0000 [148f8355a0]  Naveen Albert <asterisk@phreaknet.org>
+
+	* logger: Add custom logging capabilities
+
+	  Adds the ability for users to log to custom log levels
+	  by providing custom log level names in logger.conf. Also
+	  adds a logger show levels CLI command.
+
+	  ASTERISK-29529
+
+	  Change-Id: If082703cf81a436ae5a565c75225fa8c0554b702
+
+2021-09-17 10:57 +0000 [6698753b24]  Sean Bright <sean.bright@gmail.com>
+
+	* app_externalivr.c: Fix mixed leading whitespace in source code.
+
+	  No functional changes.
+
+	  Change-Id: I46514152c0af67f395526374aaa847ccd6a85378
+
+2021-09-17 14:58 +0000 [29ad5b18f1]  Guido Falsi <madpilot@freebsd.org>
+
+	* res_rtp_asterisk.c: Fix build failure when not building with pjproject.
+
+	  Some code has been added referencing symbols defined in a block
+	  protected by #ifdef HAVE_PJPROJECT. Protect those code parts in
+	  ifdef blocks too.
+
+	  ASTERISK-29660
+
+	  Change-Id: Ib18d4392d51ac80ca5481dabf6e498a4e3e49e6f
+
+2021-09-14 12:02 +0000 [54a9dbb2b8]  George Joseph <gjoseph@digium.com>
+
+	* pjproject: Add patch to fix trailing whitespace issue in rtpmap
+
+	  An issue was found where a particular manufacturer's phones add a
+	  trailing space to the end of the rtpmap attribute when specifying
+	  a payload type that has a "param" after the format name and clock
+	  rate. For example:
+
+	  a=rtpmap:120 opus/48000/2 \r\n
+
+	  Because pjmedia_sdp_attr_get_rtpmap currently takes everything after
+	  the second '/' up to the line end as the param, the space is
+	  included in future comparisons, which then fail if the param being
+	  compared to doesn't also have the space.
+
+	  We now use pj_scan_get() to parse the param part of rtpmap so
+	  trailing whitespace is automatically stripped.
+
+	  ASTERISK-29654
+
+	  Change-Id: Ibd0a4e243a69cde7ba9312275b13ab62ab86bc1b
+
+2021-09-13 10:18 +0000 [07c297d058]  Carlos Oliva <carlos.oliva@invoxcontact.com>
+
+	* app_mp3: Force output to 16 bits in mpg123
+
+	  In new mpg123 versions (since 1.26) the default output is 32 bits
+	  Asterisk expects the output in 16 bits, so we force the output to be on 16 bits.
+	  It will work wit new and old versions of mpg123.
+	  Thanks Thomas Orgis <thomas-forum@orgis.org> for giving the key!
+
+	  ASTERISK-29635 #close
+
+	  Change-Id: I88c7740118b5af4e895bd8b765b68ed5c11fc816
+
+2021-06-08 15:44 +0000 [5b5c358e4b]  Naveen Albert <asterisk@phreaknet.org>
+
+	* res_pjsip_caller_id: Add ANI2/OLI parsing
+
+	  Adds parsing of ANI II digits (Originating
+	  Line Information) to PJSIP, on par with
+	  what currently exists in chan_sip.
+
+	  ASTERISK-29472
+
+	  Change-Id: Ifc938a7a7d45ce33999ebf3656a542226f6d3847
+
+2021-06-28 10:37 +0000 [b760bad2b9]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_mf: Add channel agnostic MF sender
+
+	  Adds a SendMF application and PlayMF manager
+	  event to send arbitrary R1 MF tones on the
+	  current or specified channel.
+
+	  ASTERISK-29496
+
+	  Change-Id: I5d89afdbccee3f86cc702ed96d882f3d351327a4
+
+2021-09-02 18:20 +0000 [18c92353f8]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_stack: Include current location if branch fails
+
+	  Previously, the error emitted when app_stack tries
+	  to branch to a dialplan location that doesn't exist
+	  has included only the information about the attempted
+	  branch in the error log. This adds the current location
+	  as well so users can see where the branch failed in
+	  the logs.
+
+	  ASTERISK-29626
+
+	  Change-Id: Ia23502ab2ad21485a1ac74295063a8f25a6df5ce
+
+2021-09-10 09:56 +0000 [46afd61b75]  Sean Bright <sean.bright@gmail.com>
+
+	* test_http_media_cache.c: Fix copy/paste error during test deregistration.
+
+	  Change-Id: I9a3a978b2f818be464e062d97b93831b127ef28c
+
+2021-09-03 13:27 +0000 [a1fa8df0ae]  Sungtae Kim <pchero21@gmail.com>
+
+	* resource_channels.c: Fix external media data option
+
+	  Fixed the external media creation handle to handle the 'data' option correctly.
+
+	  ASTERISK-29629
+
+	  Change-Id: I22e57fe8ebf3d3e08fb2121aa4a8a52cc62e8129
+
+2021-09-02 18:57 +0000 [b8fc77a35b]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_strings: Add STRBETWEEN function
+
+	  Adds the STRBETWEEN function, which can be used to insert a
+	  substring between each character in a string. For instance,
+	  this can be used to insert pauses between DTMF tones in a
+	  string of digits.
+
+	  ASTERISK-29627
+
+	  Change-Id: Ice23009d4a8e9bb9718d2b2301d405567087d258
+
+2021-09-08 14:29 +0000 [c4037d4aa3]  Sean Bright <sean.bright@gmail.com>
+
+	* test_abstract_jb.c: Fix put and put_out_of_order memory leaks.
+
+	  We can't rely on RAII_VAR(...) to properly clean up data that is
+	  allocated within a loop.
+
+	  ASTERISK-27176 #close
+
+	  Change-Id: Ib575616101230c4f603519114ec62ebf3936882c
+
+2021-09-02 19:00 +0000 [e0111a56fa]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_env: Add DIRNAME and BASENAME functions
+
+	  Adds the DIRNAME and BASENAME functions, which are
+	  wrappers around the corresponding C library functions.
+	  These can be used to safely and conveniently work with
+	  file paths and names in the dialplan.
+
+	  ASTERISK-29628 #close
+
+	  Change-Id: Id3aeb907f65c0ff96b6e57751ff0cb49d61db7f3
+
+2021-07-26 12:46 +0000 [ddf6299b8d]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_sayfiles: Retrieve say file names
+
+	  Up until now, all of the logic used to translate
+	  arguments to the Say applications has been
+	  directly coupled to playback, preventing other
+	  modules from using this logic.
+
+	  This refactors code in say.c and adds a SAYFILES
+	  function that can be used to retrieve the file
+	  names that would be played. These can then be
+	  used in other applications or for other purposes.
+
+	  Additionally, a SayMoney application and a SayOrdinal
+	  application are added. Both SayOrdinal and SayNumber
+	  are also expanded to support integers greater than
+	  one billion.
+
+	  ASTERISK-29531
+
+	  Change-Id: If9718c89353b8e153d84add3cc4637b79585db19
+
+2021-08-09 12:41 +0000 [7df69633cf]  Naveen Albert <asterisk@phreaknet.org>
+
+	* res_tonedetect: Tone detection module
+
+	  dsp.c contains arbitrary tone detection functionality
+	  which is currently only used for fax tone recognition.
+	  This change makes this functionality publicly
+	  accessible so that other modules can take advantage
+	  of this.
+
+	  Additionally, a WaitForTone and TONE_DETECT app and
+	  function are included to allow users to do their
+	  own tone detection operations in the dialplan.
+
+	  ASTERISK-29546
+
+	  Change-Id: Ie38c395000f4fd4d04e942e8658e177f8f499b26
+
+2021-09-08 09:36 +0000 [448962d056]  George Joseph <gjoseph@digium.com>
+
+	* res_snmp: Add -fPIC to _ASTCFLAGS
+
+	  With gcc 11, res/res_snmp.c and res/snmp/agent.c need the
+	  -fPIC option added to its _ASTCFLAGS.
+
+	  ASTERISK-29634
+
+	  Change-Id: I34649c85e075fd954e578378fabf798c3f038f50
+
+2021-09-07 12:32 +0000 [26fc5f3c72]  Sean Bright <sean.bright@gmail.com>
+
+	* app_voicemail.c: Ability to silence instructions if greeting is present.
+
+	  There is an option to silence voicemail instructions but it does not
+	  take into consideration if a recorded greeting exists or not. Add a
+	  new 'S' option that does that.
+
+	  ASTERISK-29632 #close
+
+	  Change-Id: I03f2f043a9beb9d99deab302247e2a8686066fb4
+
+2021-09-04 12:07 +0000 [605dd03b36]  Sean Bright <sean.bright@gmail.com>
+
+	* term.c: Add support for extended number format terminfo files.
+
+	  ncurses 6.1 introduced an extended number format for terminfo files
+	  which the terminfo parsing in Asterisk is not able to parse. This
+	  results in some TERM values that do support color (screen-256color on
+	  Ubuntu 20.04 for example) to not get a color console.
+
+	  ASTERISK-29630 #close
+
+	  Change-Id: I27a4fcfab502219924af2d6b1c46feba92903cb3
+
+2021-09-03 00:30 +0000 [c07d531191]  Jasper Hafkenscheid <jasper.hafkenscheid@wearespindle.com>
+
+	* res_srtp: Disable parsing of not enabled cryptos
+
+	  When compiled without extended srtp crypto suites also disable parsing
+	  these from received SDP. This prevents using these, as some client
+	  implementations are not stable.
+
+	  ASTERISK-29625
+
+	  Change-Id: I7dafb29be1cdaabdc984002573f4bea87520533a
+
+2021-09-06 11:37 +0000 [695fc3dbd7]  Sean Bright <sean.bright@gmail.com>
+
+	* dns.c: Load IPv6 DNS resolvers if configured.
+
+	  IPv6 nameserver addresses are stored in different part of the
+	  __res_state structure, so look there if we appear to have support for
+	  it.
+
+	  ASTERISK-28004 #close
+
+	  Change-Id: I67067077d8a406ee996664518d9c8fbf11f6977d
+
+2021-09-08 07:52 +0000 [976521c9a2]  George Joseph <gjoseph@digium.com>
+
+	* bridge_softmix: Suppress error on topology change failure
+
+	  There are conditions under which a failure to change topology
+	  is expected so there's no need to print an ERROR message.
+
+	  ASTERISK-29618
+	  Reported by: Alexander
+
+	  Change-Id: Idc168b8588e018bf3a23769f08c4ad646086d481
+
+2021-08-31 02:50 +0000 [79d6d222d6]  sungtae kim <sungtae.kim@avoxi.com>
+
+	* resource_channels.c: Fix wrong external media parameter parse
+
+	  Fixed ARI external media handler to accept body parameters.
+
+	  ASTERISK-29622
+
+	  Change-Id: I49509c48a6cbc0fb4165bfa4f834b5e8b9ace20d
+
+2021-08-25 10:21 +0000 [5029e78f39]  Sean Bright <sean.bright@gmail.com>
+
+	* config_options: Handle ACO arrays correctly in generated XML docs.
+
+	  There are 3 separate changes here but they are all closely related:
+
+	  * Only try to set matchfield attributes on 'field' nodes
+
+	  * We need to adjust how we treat the category pointer based on the
+	    value of the category_match, to avoid memory corruption. We now
+	    generate a regex-like string when match types other than
+	    ACO_WHITELIST and ACO_BLACKLIST are used.
+
+	  * Switch app_agent_pool from ACO_BLACKLIST_ARRAY to
+	    ACO_BLACKLIST_EXACT since we only have one category we need to
+	    ignore, not two.
+
+	  ASTERISK-29614 #close
+
+	  Change-Id: I7be7bdb1bb9814f942bc6bb4fdd0a55a7b7efe1e
+
+2021-08-18 14:44 +0000 [3072c540bb]  Naveen Albert <asterisk@phreaknet.org>
+
+	* chan_iax2: Add ANI2/OLI information element
+
+	  Adds an information element for ANI2 so that
+	  Originating Line Information can be transmitted
+	  over IAX2 channels.
+
+	  ASTERISK-29605 #close
+
+	  Change-Id: Iaeacdf6ccde18eaff7f776a0f49fee87dcb549d2
+
+2021-08-31 15:03 +0000 [bbf4f30059]  Mark Murawski <markm@intellasoft.net>
+
+	* pbx_ael:  Fix crash and lockup issue regarding 'ael reload'
+
+	  Currently pbx_ael does not check if a reload is currently pending
+	  before proceeding with a reload. This can cause multiple threads to
+	  operate at the same time on what should be mutex protected data. This
+	  change adds protection to reloading to ensure only one ael reload is
+	  executing at a time.
+
+	  ASTERISK-29609 #close
+
+	  Change-Id: I5ed392ad226f6e4e7696ad742076d3e45c57af35
+
+2021-08-25 06:49 +0000 [6cc004dc5a]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_read: Allow reading # as a digit
+
+	  Allows for the digit # to be read as a digit,
+	  just like any other DTMF digit, as opposed to
+	  forcing it to be used as an end of input
+	  indicator. The default behavior remains
+	  unchanged.
+
+	  ASTERISK-18454 #close
+
+	  Change-Id: I3033432adb9d296ad227e76b540b8b4a2417665b
+
+2021-04-05 14:06 +0000 [6fbf55ac11]  Sebastien Duthil <sduthil@wazo.community>
+
+	* res_rtp_asterisk: Automatically refresh stunaddr from DNS
+
+	  This allows the STUN server to change its IP address without having to
+	  reload the res_rtp_asterisk module.
+
+	  The refresh of the name resolution occurs first when the module is
+	  loaded, then recurringly, slightly after the previous DNS answer TTL
+	  expires.
+
+	  ASTERISK-29508 #close
+
+	  Change-Id: I7955a046293f913ba121bbd82153b04439e3465f
+
+2021-08-24 20:04 +0000 [f01a0398f8]  Naveen Albert <asterisk@phreaknet.org>
+
+	* bridge_basic: Change warning to verbose if transfer cancelled
+
+	  The attended transfer feature will emit a warning if the user
+	  cancels the transfer or the attended transfer doesn't complete
+	  for any reason. Changes the warning to a verbose message,
+	  since nothing is actually wrong here.
+
+	  ASTERISK-29612 #close
+
+	  Change-Id: I64c93cdb21360a0a8d45e9cb6db3af8168f66e6d
+
+2021-08-20 15:35 +0000 [92f9ae32a8]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_queue: Don't reset queue stats on reload
+
+	  Prevents reloads of app_queue from also resetting
+	  queue statistics.
+
+	  Also preserves individual queue agent statistics
+	  if we're just reloading members.
+
+	  ASTERISK-28701
+
+	  Change-Id: Ib5d4cdec175e44de38ef0f6ede4a7701751766f1
+
+2021-08-25 09:23 +0000 [63d27af3ca]  Alexander Traud <pabstraud@compuserve.com>
+
+	* res_rtp_asterisk: sqrt(.) requires the header math.h.
+
+	  ASTERISK-29616
+
+	  Change-Id: I6c01623926bf10ccac32612687a50fdab3ba0900
+
+2021-08-25 09:29 +0000 [fbdd8a7f8a]  Alexander Traud <pabstraud@compuserve.com>
+
+	* dialplan: Add one static and fix two whitespace errors.
+
+	  Change-Id: Ia14d515ab63e773097adc6af772ca7123a392f83
+
+2021-06-19 23:36 +0000 [466eb4a52b]  Sarah Autumn <sarah@connectionsmuseum.org>
+
+	* sig_analog: Changes to improve electromechanical signalling compatibility
+
+	  This changeset is intended to address compatibility issues encountered
+	  when interfacing Asterisk to electromechanical telephone switches that
+	  implement ANI-B, ANI-C, or ANI-D.
+
+	  In particular the behaviours that this impacts include:
+
+	   - FGC-CAMA did not work at all when using MF signaling. Modified the
+	     switch case block to send calls to the correct part of the
+	     signaling-handling state machine.
+
+	   - For FGC-CAMA operation, the delay between called number ST and
+	     second wink for ANI spill has been made configurable; previously
+	     all calls were made to wait for one full second.
+
+	   - After the ANI spill, previous behavior was to require a 'ST' tone
+	     to advance the call.  This has been changed to allow 'STP' 'ST2P'
+	     or 'ST3P' as well, for compatibility with ANI-D.
+
+	   - Store ANI2 (ANI INFO) digits in the CALLERID(ANI2) channel variable.
+
+	   - For calls with an ANI failure, No. 1 Crossbar switches will send
+	     forward a single-digit failure code, with no calling number digits
+	     and no ST pulse to terminate the spill.  I've made the ANI timeout
+	     configurable so to reduce dead air time on calls with ANI fail.
+
+	   - ANI info digits configurable.  Modern digital switches will send 2
+	     digits, but ANI-B sends only a single info digit.  This caused the
+	     ANI reported by Asterisk to be misaligned.
+
+	   - Changed a confusing log message to be more informative.
+
+	  ASTERISK-29518
+
+	  Change-Id: Ib7e27d987aee4ed9bc3663c57ef413e21b404256
+
+2021-08-05 11:55 +0000 [c4839c04b6]  Andre Barbosa <andre.emanuel.barbosa@gmail.com>
+
+	* media_cache: Don't lock when curl the remote file
+
+	  When playing a remote sound file, which is not in cache, first we need
+	  to download it with ast_bucket_file_retrieve.
+
+	  This can take a while if the remote host is slow. The current CURL
+	  timeout is 180secs, so in extreme situations, it can take 3 minutes to
+	  return.
+
+	  Because ast_media_cache_retrieve has a lock on all function, while we
+	  are waiting for the delayed download, Asterisk is not able to play any
+	  more files, even the files already cached locally.
+
+	  ASTERISK-29544 #close
+
+	  Change-Id: I8d4142b463ae4a1d4c41bff2bf63324821567408
+
+2021-08-16 08:25 +0000 [84f2bf4307]  George Joseph <gjoseph@digium.com>
+
+	* res_pjproject: Allow mapping to Asterisk TRACE level
+
+	  Allow mapping pjproject log messages to the Asterisk TRACE
+	  log level.  The defaults were also changes to log pjproject
+	  levels 3,4 to DEBUG and 5,6 to TRACE.  Previously 3,4,5,6
+	  all went to DEBUG.
+
+	  ASTERISK-29582
+
+	  Change-Id: I859a37a8dec263ed68099709cfbd3e665324c72d
+
+2021-08-12 16:02 +0000 [314d8776dc]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_milliwatt: Timing fix
+
+	  The Milliwatt application uses incorrect tone timings
+	  that cause it to play the 1004 Hz tone constantly.
+
+	  This adds an option to enable the correct timing
+	  behavior, so that the Milliwatt application can
+	  be used for milliwatt test lines. The default behavior
+	  remains unchanged for compatability reasons, even
+	  though it is incorrect.
+
+	  ASTERISK-29575 #close
+
+	  Change-Id: I73ccc6c6fcaa31931c6fff3b85ad1805b2ce9d8c
+
+2021-06-28 09:25 +0000 [85ef06d300]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_math: Return integer instead of float if possible
+
+	  The MIN, MAX, and ABS functions all support float
+	  arguments, but currently return floats even if the
+	  arguments are all integers and the response is
+	  a whole number, in which case the user is likely
+	  expecting an integer. This casts the float to an integer
+	  before printing into the response buffer if possible.
+
+	  ASTERISK-29495
+
+	  Change-Id: I902d29eacf3ecd0f8a6a5e433c97f0421d205488
+
+2021-08-04 09:46 +0000 [5c9d7a0373]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_morsecode: Add American Morse code
+
+	  Previously, the Morsecode application only supported international
+	  Morse code. This adds support for American Morse code and adds an
+	  option to configure the frequency used in off intervals.
+
+	  Additionally, the application checks for hangup between tones
+	  to prevent application execution from continuing after hangup.
+
+	  ASTERISK-29541
+
+	  Change-Id: I172431a2e18e6527d577e74adfb05b154cba7bd4
+
+2021-08-04 14:16 +0000 [498db70884]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_scramble: Audio scrambler function
+
+	  Adds a function to scramble audio on a channel using
+	  whole spectrum frequency inversion. This can be used
+	  as a privacy enhancement with applications like
+	  ChanSpy or other potentially sensitive audio.
+
+	  ASTERISK-29542
+
+	  Change-Id: I01020769d91060a1f56a708eb405f87648d1a67e
+
+2021-08-04 19:28 +0000 [a099f13a20]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_originate: Add ability to set codecs
+
+	  A list of codecs to use for dialplan-originated calls can
+	  now be specified in Originate, similar to the ability
+	  in call files and the manager action.
+
+	  Additionally, we now default to just using the slin codec
+	  for originated calls, rather than all the slin* codecs up
+	  through slin192, which has been known to cause issues
+	  and inconsistencies from AMI and call file behavior.
+
+	  ASTERISK-29543
+
+	  Change-Id: I96a1aeb83d54b635b7a51e1b4680f03791622883
+
+2021-08-16 11:11 +0000 [137bd7fe65]  Alexander Traud <pabstraud@compuserve.com>
+
+	* BuildSystem: Remove two dead exceptions for compiler Clang.
+
+	  Commit 305ce3d added -Wno-parentheses-equality to Makefile.rules,
+	  turning the previous two warning suppressions from commit e9520db
+	  redundant. Let us remove the latter.
+
+	  Change-Id: I0b471254b31e6e05902062761dded4b3e626c7ac
+
+2021-08-16 14:31 +0000 [0ca3ebe7cd]  Naveen Albert <asterisk@phreaknet.org>
+
+	* chan_alsa, chan_sip: Add replacement to moduleinfo
+
+	  Adds replacement modules to the moduleinfo for
+	  chan_alsa and chan_sip.
+
+	  ASTERISK-29601 #close
+
+	  Change-Id: I7a4877b0d5c0c17e088e8fa8ebbfa9a195223cbc
+
+2021-08-17 08:11 +0000 [0ddeac0e36]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_monitor: Disable building by default.
+
+	  ASTERISK-29602
+
+	  Change-Id: I6f0af0a959409cdbc6b185b1604301bafc872a5a
+
+2021-08-16 13:47 +0000 [fcbf0a6699]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* muted: Remove deprecated application.
+
+	  ASTERISK-29600
+
+	  Change-Id: I0ae1c6a2996da43217126f094de90761314dcf82
+
+2021-08-16 13:39 +0000 [6d5b66f5f3]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* conf2ael: Remove deprecated application.
+
+	  ASTERISK-29599
+
+	  Change-Id: I75dc77162926fb17e7c6caf8f04e3aabd792fb0c
+
+2021-08-16 13:26 +0000 [800fd84af6]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_config_sqlite: Remove deprecated module.
+
+	  ASTERISK-29598
+
+	  Change-Id: I8ef17023f55bf01f2e309b06f4778a8ca7252c91
+
+2021-08-16 13:22 +0000 [20b2741232]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* chan_vpb: Remove deprecated module.
+
+	  ASTERISK-29597
+
+	  Change-Id: I19bb39eed0257ddfef453eb2df5646d073d50fe1
+
+2021-08-16 13:18 +0000 [1eb2d85c99]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* chan_misdn: Remove deprecated module.
+
+	  ASTERISK-29596
+
+	  Change-Id: Ibae9490c1b35cadbf7028d24610f745277c8535e
+
+2021-08-16 13:13 +0000 [6ecc48086c]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* chan_nbs: Remove deprecated module.
+
+	  ASTERISK-29595
+
+	  Change-Id: Ib5c7d43a780f2fb94cee90738e4c1af211ae4a33
+
+2021-08-16 13:10 +0000 [6cc948f94e]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* chan_phone: Remove deprecated module.
+
+	  ASTERISK-29594
+
+	  Change-Id: I79a9961cb5062fadbccb0ea93f087bdd32685316
+
+2021-08-16 13:06 +0000 [95f3a4a9ad]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* chan_oss: Remove deprecated module.
+
+	  ASTERISK-29593
+
+	  Change-Id: Ib53a42ad974c63871344b95078c61c188e43da99
+
+2021-08-16 13:04 +0000 [30d5264409]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* cdr_syslog: Remove deprecated module.
+
+	  ASTERISK-29592
+
+	  Change-Id: Ic8eb6a2100ad5bc3b48338a6d0a6cfa70ecbc50f
+
+2021-08-16 12:56 +0000 [9e5269c7ae]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_dahdiras: Remove deprecated module.
+
+	  ASTERISK-29591
+
+	  Change-Id: I021d37b729631d40f84e35bb21e2893777be1858
+
+2021-08-16 12:55 +0000 [98e0745a14]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_nbscat: Remove deprecated module.
+
+	  ASTERISK-29590
+
+	  Change-Id: I87cf0f536b77d222c8eda003376ac47fae86ed43
+
+2021-08-16 12:52 +0000 [13963e643b]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_image: Remove deprecated module.
+
+	  ASTERISK-29589
+
+	  Change-Id: I8057eb2ca1ca4c3b27ed2fe04bea10e9cb551cdd
+
+2021-08-16 12:50 +0000 [7c642c55b8]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_url: Remove deprecated module.
+
+	  ASTERISK-29588
+
+	  Change-Id: If846d40b37c5b646bcd7326111db280529a5971b
+
+2021-08-16 12:48 +0000 [24e21e59af]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_fax: Remove deprecated module.
+
+	  ASTERISK-29587
+
+	  Change-Id: I038237bbb56b1161d7d5e20cda11ed32e13d3ca2
+
+2021-08-16 12:46 +0000 [1f1a87a97b]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_ices: Remove deprecated module.
+
+	  ASTERISK-29586
+
+	  Change-Id: I1e0a4535135b00938b609fe0ccba9bbddbac93ad
+
+2021-08-16 12:43 +0000 [2f510d7a88]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_mysql: Remove deprecated module.
+
+	  ASTERISK-29585
+
+	  Change-Id: I262930d0387d043f2a3345e8a977b314528059bf
+
+2021-08-16 12:39 +0000 [2a0e383e4f]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* cdr_mysql: Remove deprecated module.
+
+	  ASTERISK-29584
+
+	  Change-Id: I4bd3695d089121f810d692a82361d39d2f97ae39
+
+2021-08-10 12:41 +0000 [743e057bb4]  Sean Bright <sean.bright@gmail.com>
+
+	* mgcp: Remove dead debug code
+
+	  ASTERISK-20339 #close
+
+	  Change-Id: I36f364aaa1971241d8f3ea1a5909b463d185a2d5
+
+2021-08-11 06:15 +0000 [93870e7bb4]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* policy: Deprecate modules and add versions to others.
+
+	  app_meetme is deprecated in 19, to be removed in 21.
+	  app_osplookup is deprecated in 19, to be removed in 21.
+	  chan_alsa is deprecated in 19, to be removed in 21.
+	  chan_mgcp is deprecated in 19, to be removed in 21.
+	  chan_skinny is deprecated in 19, to be removed in 21.
+	  res_pktccops is deprecated in 19, to be removed in 21.
+	  app_macro was deprecated in 16, to be removed in 21.
+	  chan_sip was deprecated in 17, to be removed in 21.
+	  res_monitor was deprecated in 16, to be removed in 21.
+
+	  ASTERISK-29548
+	  ASTERISK-29549
+	  ASTERISK-29550
+	  ASTERISK-29551
+	  ASTERISK-29552
+	  ASTERISK-29553
+	  ASTERISK-29558
+	  ASTERISK-29567
+	  ASTERISK-29572
+
+	  Change-Id: Ic3bee31a10d42c4b3bbc913d893f7b2a28a27131
+
+2021-06-16 15:30 +0000 [6a89266b5b]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_frame_drop: New function
+
+	  Adds function to selectively drop specified frames
+	  in the TX or RX direction on a channel, including
+	  control frames.
+
+	  ASTERISK-29478
+
+	  Change-Id: I8147c9d55d74e2e48861edba6b22f930920541ec
+
+2021-08-02 12:33 +0000 [8a6c9c3a76]  Alexander Traud <pabstraud@compuserve.com>
+
+	* aelparse: Accept an included context with timings.
+
+	  With Asterisk 1.6.0, in the main parser for the configuration file
+	  extensions.conf, the separator was changed from vertical bar to comma.
+	  However, the first separator was not changed in aelparse; it still had
+	  to be a vertical bar, and no comma was allowed.
+
+	  Additionally, this change allows the vertical bar for the first and
+	  last parameter again, even in the main parser, because the vertical bar
+	  was still accepted for the other parameters.
+
+	  ASTERISK-29540
+
+	  Change-Id: I882e17c73adf4bf2f20f9046390860d04a9f8d81
+
+2021-08-03 11:30 +0000 [049c7c1361]  Kevin Harwell <kharwell@sangoma.com>
+
+	* format_ogg_speex: Implement a "not supported" write handler
+
+	  This format did not specify a "write" handler, so when attempting to write
+	  to it (ast_writestream) a crash would occur.
+
+	  This patch adds a default handler that simply issues a "not supported"
+	  warning, thus no longer crashing.
+
+	  ASTERISK-29539
+
+	  Change-Id: I8f6ddc7cc3b15da30803be3b1cf68e2ba0fbce91
+
+2021-06-28 08:48 +0000 [b5709e610e]  Naveen Albert <asterisk@phreaknet.org>
+
+	* cdr_adaptive_odbc: Prevent filter warnings
+
+	  Previously, if CDR filters were used so that
+	  not all CDR records used all sections defined
+	  in cdr_adaptive_odbc.conf, then warnings will
+	  always be emitted (if each CDR record is unique
+	  to a particular section, n-1 warnings to be
+	  specific).
+
+	  This turns the offending warning log into
+	  a verbose message like the other one, since
+	  this behavior is intentional and not
+	  indicative of anything wrong.
+
+	  ASTERISK-29494
+
+	  Change-Id: Ifd314fa9298722bc99494d5ca2658a5caa94a5f8
+
+2021-07-25 16:53 +0000 [0e023e6cf1]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_queue: Allow streaming multiple announcement files
+
+	  Allows multiple files comprising an agent announcement
+	  to be played by separating on the ampersand, similar
+	  to the multi-file support in other Asterisk applications.
+
+	  ASTERISK-29528
+
+	  Change-Id: Iec600d8cd5ba14aa1e4e37f906accb356cd7891a
+
+2021-04-13 02:36 +0000 [4f437ea1f4]  Igor Goncharovsky <igorg@iqtek.ru>
+
+	* res_pjsip_header_funcs: Add PJSIP_HEADERS() ability to read header by pattern
+
+	  PJSIP currently does not provide a function to replace SIP_HEADERS() function to get a list of headers from INVITE request.
+	  It may be used to get all X- headers in case the actual set and names of headers unknown.
+
+	  ASTERISK-29389
+
+	  Change-Id: Ic09d395de71a0021e0d6c5c29e1e19d689079f8b
+
+2021-07-08 07:34 +0000 [728a52fb61]  Rijnhard Hessel <rijnhard@teleforge.co.za>
+
+	* res_statsd: handle non-standard meter type safely
+
+	  Meter types are not well supported,
+	  lacking support in telegraf, datadog and the official statsd servers.
+	  We deprecate meters and provide a compliant fallback for any existing usages.
+
+	  A flag has been introduced to allow meters to fallback to counters.
+
+
+	  ASTERISK-29513
+
+	  Change-Id: I5fcb385983a1b88f03696ff30a26b55c546a1dd7
+
+2021-06-16 15:26 +0000 [fa7d147e1b]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_dtmfstore: New application to store digits
+
+	  Adds application to asynchronously collect digits
+	  dialed on a channel in the TX or RX direction
+	  using a framehook and stores them in a specified
+	  variable, up to a configurable number of digits.
+
+	  ASTERISK-29477
+
+	  Change-Id: I51aa93fc9507f7636ac44806c4420ce690423e6f
+
+2021-07-22 11:39 +0000 [de3f5350de]  under <pcapdump@gmail.com>
+
+	* codec_builtin.c: G729 audio gets corrupted by Asterisk due to smoother
+
+	  If Asterisk gets G.729 6-byte VAD frames inbound, then at outbound Asterisk sends this G.729 stream with non-continuous timestamps.
+	  This makes the audio stream not-playable at the receiver side.
+	  Linphone isn't able to play such an audio - lots of disruptions are heard.
+	  Also I had complains of bad audio from users which use other types of phones.
+
+	  After debugging, I found this is a regression connected with RTP Smoother (main/smoother.c).
+
+	  Smoother has a special code to handle G.729 VAD frames (search for AST_SMOOTHER_FLAG_G729 in smoother.c).
+
+	  However, this flag is never set in Asterisk-12 and newer.
+	  Previously it has been set (see Asterisk-11).
+
+	  ASTERISK-29526 #close
+
+	  Change-Id: I6f51ecb1a3ecd9c6d59ec5a6811a27446e17065d
+
+2021-07-23 11:00 +0000 [6428124b06]  Sean Bright <sean.bright@gmail.com>
+
+	* res_http_media_cache: Cleanup audio format lookup in HTTP requests
+
+	  Asterisk first looks at the end of the URL to determine the file
+	  extension of the returned audio, which in many cases will not work
+	  because the URL may end with a query string or a URL fragment. If that
+	  fails, Asterisk then looks at the Content-Type header and then finally
+	  parses the URL to get the extension.
+
+	  The order has been changed such that we look at the Content-Type
+	  header first, followed by looking for the extension of the parsed
+	  URL. We no longer look at the end of the URL, which was error prone.
+
+	  ASTERISK-29527 #close
+
+	  Change-Id: I1e3f83b339ef2b80661704717c23568536511032
+
+2021-07-27 07:53 +0000 [d0f189a5c9]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* docs: Remove embedded macro in WaitForCond XML documentation.
+
+	  Change-Id: I40c6514e1843e320f3cbe0b2c70d4a98c0e35b9c
+
+2021-07-21 10:14 +0000 [db7b025532]  Ben Ford <bford@digium.com>
+
+	* Update AMI and ARI versions for Asterisk 20.
+
+	  Bumped AMI and ARI versions for the next major Asterisk version (20).
+
+	  Change-Id: I2e65794f206d443178ab6895767fb53f04cc3e6a
+
+2021-06-14 13:28 +0000 [e8cda4b32c]  Kevin Harwell <kharwell@sangoma.com>
+
+	* AST-2021-009 - pjproject-bundled: Avoid crash during handshake for TLS
+
+	  If an SSL socket parent/listener was destroyed during the handshake,
+	  depending on timing, it was possible for the handling callback to
+	  attempt access of it after the fact thus causing a crash.
+
+	  ASTERISK-29415 #close
+
+	  Change-Id: I105dacdcd130ea7fdd4cf2010ccf35b5eaf1432d
+
+2021-05-10 17:59 +0000 [1b62831f2c]  Kevin Harwell <kharwell@sangoma.com>
+
+	* AST-2021-008 - chan_iax2: remote crash on unsupported media format
+
+	  If chan_iax2 received a packet with an unsupported media format, for
+	  example vp9, then it would set the frame's format to NULL. This could
+	  then result in a crash later when an attempt was made to access the
+	  format.
+
+	  This patch makes it so chan_iax2 now ignores/drops frames received
+	  with unsupported media format types.
+
+	  ASTERISK-29392 #close
+
+	  Change-Id: Ifa869a90dafe33eed8fd9463574fe6f1c0ad3eb1
+
+2021-04-28 07:36 +0000 [ec16d2ecbd]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* AST-2021-007 - res_pjsip_session: Don't offer if no channel exists.
+
+	  If a re-INVITE is received after we have sent a BYE request then it
+	  is possible for no channel to be present on the session. If this
+	  occurs we allow PJSIP to produce the offer instead. Since the call
+	  is being hung up if it produces an incorrect offer it doesn't
+	  actually matter. This also ensures that code which produces SDP
+	  does not need to handle if a channel is not present.
+
+	  ASTERISK-29381
+
+	  Change-Id: I673cb88c432f38f69b2e0851d55cc57a62236042
+
+2021-07-21 09:59 +0000 [e6ddbe0922]  Asterisk Development Team <asteriskteam@digium.com>
+
+	* Update CHANGES and UPGRADE.txt for 19.0.0
+2021-10-13 10:44 +0000  Asterisk Development Team <asteriskteam@digium.com>
+
+	* asterisk 19.0.0-rc1 Released.
+
+2021-10-13 05:21 +0000 [9ff955f4d1]  Asterisk Development Team <asteriskteam@digium.com>
+
+	* Update CHANGES and UPGRADE.txt for 19.0.0
+2021-10-07 12:50 +0000 [9175012a12]  Sean Bright <sean.bright@gmail.com>
+
+	* Makefile: Use basename in a POSIX-compliant way.
+
+	  If you aren't using GNU coreutils, chances are that your basename
+	  doesn't know about the -s argument. Luckily for us, basename does what
+	  we need it do even without the -s argument.
+
+	  Change-Id: I8b81a429bb037b997ee6640ff8a2b5e860962bb7
+
+2021-10-05 19:59 +0000 [1f5ac24fa3]  Mark Murawski <markm@intellasoft.net>
+
+	* pbx_ael:  Fix crash and lockup issue regarding 'ael reload'
+
+	  Avoid infinite recursion and crash
+
+	  Change-Id: I8ed05ec3aa2806c50c77edc5dd0cd4e4fa08b3f4
+
+2021-05-24 13:04 +0000 [32ea7c7ca5]  Naveen Albert <asterisk@phreaknet.org>
+
+	* chan_iax2: Add encryption for RSA authentication
+
+	  Adds support for encryption to RSA-authenticated
+	  calls. Also prevents crashes if an RSA IAX2 call
+	  is initiated to a switch requiring encryption
+	  but no secret is provided.
+
+	  ASTERISK-20219
+
+	  Change-Id: I18f1f9d7c59b4f9cffa00f3b94a4c875846efd40
+
+2021-07-19 11:34 +0000 [9d04535bbd]  Matthew Kern <mkern@alconconstruction.com>
+
+	* res_pjsip_t38: bind UDPTL sessions like RTP
+
+	  In res_pjsip_sdp_rtp, the bind_rtp_to_media_address option and the
+	  fallback use of the transport's bind address solve problems sending
+	  media on systems that cannot send ipv4 packets on ipv6 sockets, and
+	  certain other situations. This change extends both of these behaviors
+	  to UDPTL sessions as well in res_pjsip_t38, to fix fax-specific
+	  problems on these systems, introducing a new option
+	  endpoint/t38_bind_udptl_to_media_address.
+
+	  ASTERISK-29402
+
+	  Change-Id: I87220c0e9cdd2fe9d156846cb906debe08c63557
+
+2021-09-29 12:58 +0000 [60bbfe4572]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_read: Fix null pointer crash
+
+	  If the terminator character is not explicitly specified
+	  and an indications tone is used for reading a digit,
+	  there is no null pointer check so Asterisk crashes.
+	  This prevents null usage from occuring.
+
+	  ASTERISK-29673 #close
+
+	  Change-Id: Ie941833e123c3dbfb88371b5de5edbbe065514ac
+
+2021-09-29 04:32 +0000 [576119e076]  Jean Aunis <jean.aunis@prescom.fr>
+
+	* res_rtp_asterisk: fix memory leak
+
+	  Add missing reference decrement in rtp_deallocate_transport()
+
+	  ASTERISK-29671
+
+	  Change-Id: I8d22dbedb90e8dade0829b7a28372f404b07caa9
+
+2021-09-19 15:08 +0000 [f3ff893310]  Shloime Rosenblum <shloimerosenblum@gmail.com>
+
+	* main/say.c: Support future dates with Q and q format params
+
+	  The current versions do not support future dates in all say application when using the 'Q' or 'q' format parameter and says "today" for everything that is greater than today
+
+	  ASTERISK-29637
+
+	  Change-Id: I1fb1cef0ce3c18d87b1fc94ea309d13bc344af02
+
+2021-07-21 16:36 +0000 [6a04c43035]  Joseph Nadiv <ynadiv@corpit.xyz>
+
+	* res_pjsip_registrar: Remove unavailable contacts if exceeds max_contacts
+
+	  The behavior of max_contacts and remove_existing are connected.  If
+	  remove_existing is enabled, the soonest expiring contacts are removed.
+	  This may occur when there is an unavailable contact.  Similarly,
+	  when remove_existing is not enabled, registrations from good
+	  endpoints are rejected in favor of retaining unavailable contacts.
+
+	  This commit adds a new AOR option remove_unavailable, and the effect
+	  of this setting will depend on remove_existing.  If remove_existing
+	  is set to no, we will still remove unavailable contacts when they
+	  exceed max_contacts, if there are any. If remove_existing is set to
+	  yes, we will prioritize the removal of unavailable contacts before
+	  those that are expiring soonest.
+
+	  ASTERISK-29525
+
+	  Change-Id: Ia2711b08f2b4d1177411b1be23e970d7fdff5784
+
+2021-09-23 09:13 +0000 [35a94ec708]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* ari: Ignore invisible bridges when listing bridges.
+
+	  When listing bridges we go through the ones present in
+	  ARI, get their snapshot, turn it into JSON, and add it
+	  to the payload we ultimately return.
+
+	  An invisible "dial bridge" exists within ARI that would
+	  also try to be added to this payload if the channel
+	  "create" and "dial" routes were used. This would ultimately
+	  fail due to invisible bridges having no snapshot
+	  resulting in the listing of bridges failing.
+
+	  This change makes it so that the listing of bridges
+	  ignores invisible ones.
+
+	  ASTERISK-29668
+
+	  Change-Id: I14fa4b589b4657d1c2a5226b0f527f45a0cd370a
+
+2021-09-19 06:14 +0000 [13ec117595]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_vmcount: Add support for multiple mailboxes
+
+	  Allows multiple mailboxes to be specified for VMCOUNT
+	  instead of just one.
+
+	  ASTERISK-29661 #close
+
+	  Change-Id: I9108528300795fd5b607efa9d4dd7b74be031813
+
+2021-09-21 09:58 +0000 [52b5821694]  Sean Bright <sean.bright@gmail.com>
+
+	* message.c: Support 'To' header override with AMI's MessageSend.
+
+	  The MessageSend AMI action has been updated to allow the Destination
+	  and the To addresses to be provided separately. This brings the
+	  MessageSend manager command in line with the capabilities of the
+	  MessageSend dialplan application.
+
+	  ASTERISK-29663 #close
+
+	  Change-Id: I8513168d3e189a9fed88aaab6f5547ccb50d332c
+
+2021-09-15 13:21 +0000 [f38c7d67d3]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_channel: Add CHANNEL_EXISTS function.
+
+	  Adds a function to check for the existence of a channel by
+	  name or by UNIQUEID.
+
+	  ASTERISK-29656 #close
+
+	  Change-Id: Ib464e9eb6e13dc683a846286798fecff4fd943cb
+
+2021-09-05 13:11 +0000 [eff78c8549]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_queue: Fix hint updates for included contexts
+
+	  Previously, if custom hints were used with the hint:
+	  format in app_queue, when device state changes occured,
+	  app_queue would only do a literal string comparison of
+	  the context used for the hint in app_queue and the context
+	  of the hint which just changed state. This caused hints
+	  to not update and become stale if the context associated
+	  with the agent included the context which actually changes
+	  state, essentially completely breaking device state for
+	  any such agents defined in this manner.
+
+	  This fix adds an additional check to ensure that included
+	  contexts are also compared against the context which changed
+	  state, so that the behavior is correct no matter whether the
+	  context is specified to app_queue directly or indirectly.
+
+	  ASTERISK-29578 #close
+
+	  Change-Id: I8caf2f8da8157ef3d9ea71a8568c1eec95592b78
+
+2021-09-10 09:40 +0000 [ff493d6f7d]  Sean Bright <sean.bright@gmail.com>
+
+	* res_http_media_cache.c: Compare unaltered MIME types.
+
+	  Rather than stripping parameters from Content-Type headers before
+	  comparison, first try to compare the whole string. If no match is
+	  found, strip the parameters and try that way.
+
+	  ASTERISK-29275 #close
+
+	  Change-Id: I2963c8ecbb3a9605b78b6421c415108d77a66a0f
+
+2021-07-25 17:19 +0000 [eb874f92db]  Naveen Albert <asterisk@phreaknet.org>
+
+	* logger: Add custom logging capabilities
+
+	  Adds the ability for users to log to custom log levels
+	  by providing custom log level names in logger.conf. Also
+	  adds a logger show levels CLI command.
+
+	  ASTERISK-29529
+
+	  Change-Id: If082703cf81a436ae5a565c75225fa8c0554b702
+
+2021-09-17 10:57 +0000 [245778a756]  Sean Bright <sean.bright@gmail.com>
+
+	* app_externalivr.c: Fix mixed leading whitespace in source code.
+
+	  No functional changes.
+
+	  Change-Id: I46514152c0af67f395526374aaa847ccd6a85378
+
+2021-09-17 14:58 +0000 [675adbf0f5]  Guido Falsi <madpilot@freebsd.org>
+
+	* res_rtp_asterisk.c: Fix build failure when not building with pjproject.
+
+	  Some code has been added referencing symbols defined in a block
+	  protected by #ifdef HAVE_PJPROJECT. Protect those code parts in
+	  ifdef blocks too.
+
+	  ASTERISK-29660
+
+	  Change-Id: Ib18d4392d51ac80ca5481dabf6e498a4e3e49e6f
+
+2021-09-14 12:02 +0000 [3d6e133ccf]  George Joseph <gjoseph@digium.com>
+
+	* pjproject: Add patch to fix trailing whitespace issue in rtpmap
+
+	  An issue was found where a particular manufacturer's phones add a
+	  trailing space to the end of the rtpmap attribute when specifying
+	  a payload type that has a "param" after the format name and clock
+	  rate. For example:
+
+	  a=rtpmap:120 opus/48000/2 \r\n
+
+	  Because pjmedia_sdp_attr_get_rtpmap currently takes everything after
+	  the second '/' up to the line end as the param, the space is
+	  included in future comparisons, which then fail if the param being
+	  compared to doesn't also have the space.
+
+	  We now use pj_scan_get() to parse the param part of rtpmap so
+	  trailing whitespace is automatically stripped.
+
+	  ASTERISK-29654
+
+	  Change-Id: Ibd0a4e243a69cde7ba9312275b13ab62ab86bc1b
+
+2021-09-13 10:18 +0000 [ad1f7fae70]  Carlos Oliva <carlos.oliva@invoxcontact.com>
+
+	* app_mp3: Force output to 16 bits in mpg123
+
+	  In new mpg123 versions (since 1.26) the default output is 32 bits
+	  Asterisk expects the output in 16 bits, so we force the output to be on 16 bits.
+	  It will work wit new and old versions of mpg123.
+	  Thanks Thomas Orgis <thomas-forum@orgis.org> for giving the key!
+
+	  ASTERISK-29635 #close
+
+	  Change-Id: I88c7740118b5af4e895bd8b765b68ed5c11fc816
+
+2021-06-28 10:37 +0000 [203e73f5af]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_mf: Add channel agnostic MF sender
+
+	  Adds a SendMF application and PlayMF manager
+	  event to send arbitrary R1 MF tones on the
+	  current or specified channel.
+
+	  ASTERISK-29496
+
+	  Change-Id: I5d89afdbccee3f86cc702ed96d882f3d351327a4
+
+2021-06-08 15:44 +0000 [f8bf5e7b47]  Naveen Albert <asterisk@phreaknet.org>
+
+	* res_pjsip_caller_id: Add ANI2/OLI parsing
+
+	  Adds parsing of ANI II digits (Originating
+	  Line Information) to PJSIP, on par with
+	  what currently exists in chan_sip.
+
+	  ASTERISK-29472
+
+	  Change-Id: Ifc938a7a7d45ce33999ebf3656a542226f6d3847
+
+2021-09-02 18:20 +0000 [5fe3a745e4]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_stack: Include current location if branch fails
+
+	  Previously, the error emitted when app_stack tries
+	  to branch to a dialplan location that doesn't exist
+	  has included only the information about the attempted
+	  branch in the error log. This adds the current location
+	  as well so users can see where the branch failed in
+	  the logs.
+
+	  ASTERISK-29626
+
+	  Change-Id: Ia23502ab2ad21485a1ac74295063a8f25a6df5ce
+
+2021-09-10 09:56 +0000 [f26505d615]  Sean Bright <sean.bright@gmail.com>
+
+	* test_http_media_cache.c: Fix copy/paste error during test deregistration.
+
+	  Change-Id: I9a3a978b2f818be464e062d97b93831b127ef28c
+
+2021-09-02 18:57 +0000 [d5a53efb4f]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_strings: Add STRBETWEEN function
+
+	  Adds the STRBETWEEN function, which can be used to insert a
+	  substring between each character in a string. For instance,
+	  this can be used to insert pauses between DTMF tones in a
+	  string of digits.
+
+	  ASTERISK-29627
+
+	  Change-Id: Ice23009d4a8e9bb9718d2b2301d405567087d258
+
+2021-09-03 13:27 +0000 [4d9ba65c53]  Sungtae Kim <pchero21@gmail.com>
+
+	* resource_channels.c: Fix external media data option
+
+	  Fixed the external media creation handle to handle the 'data' option correctly.
+
+	  ASTERISK-29629
+
+	  Change-Id: I22e57fe8ebf3d3e08fb2121aa4a8a52cc62e8129
+
+2021-09-08 14:29 +0000 [085cc94f16]  Sean Bright <sean.bright@gmail.com>
+
+	* test_abstract_jb.c: Fix put and put_out_of_order memory leaks.
+
+	  We can't rely on RAII_VAR(...) to properly clean up data that is
+	  allocated within a loop.
+
+	  ASTERISK-27176 #close
+
+	  Change-Id: Ib575616101230c4f603519114ec62ebf3936882c
+
+2021-09-02 19:00 +0000 [71b021433f]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_env: Add DIRNAME and BASENAME functions
+
+	  Adds the DIRNAME and BASENAME functions, which are
+	  wrappers around the corresponding C library functions.
+	  These can be used to safely and conveniently work with
+	  file paths and names in the dialplan.
+
+	  ASTERISK-29628 #close
+
+	  Change-Id: Id3aeb907f65c0ff96b6e57751ff0cb49d61db7f3
+
+2021-07-26 12:46 +0000 [0b8ae58e67]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_sayfiles: Retrieve say file names
+
+	  Up until now, all of the logic used to translate
+	  arguments to the Say applications has been
+	  directly coupled to playback, preventing other
+	  modules from using this logic.
+
+	  This refactors code in say.c and adds a SAYFILES
+	  function that can be used to retrieve the file
+	  names that would be played. These can then be
+	  used in other applications or for other purposes.
+
+	  Additionally, a SayMoney application and a SayOrdinal
+	  application are added. Both SayOrdinal and SayNumber
+	  are also expanded to support integers greater than
+	  one billion.
+
+	  ASTERISK-29531
+
+	  Change-Id: If9718c89353b8e153d84add3cc4637b79585db19
+
+2021-08-09 12:41 +0000 [a94b51ee60]  Naveen Albert <asterisk@phreaknet.org>
+
+	* res_tonedetect: Tone detection module
+
+	  dsp.c contains arbitrary tone detection functionality
+	  which is currently only used for fax tone recognition.
+	  This change makes this functionality publicly
+	  accessible so that other modules can take advantage
+	  of this.
+
+	  Additionally, a WaitForTone and TONE_DETECT app and
+	  function are included to allow users to do their
+	  own tone detection operations in the dialplan.
+
+	  ASTERISK-29546
+
+	  Change-Id: Ie38c395000f4fd4d04e942e8658e177f8f499b26
+
+2021-09-08 09:36 +0000 [df63a99337]  George Joseph <gjoseph@digium.com>
+
+	* res_snmp: Add -fPIC to _ASTCFLAGS
+
+	  With gcc 11, res/res_snmp.c and res/snmp/agent.c need the
+	  -fPIC option added to its _ASTCFLAGS.
+
+	  ASTERISK-29634
+
+	  Change-Id: I34649c85e075fd954e578378fabf798c3f038f50
+
+2021-09-04 12:07 +0000 [61136fd297]  Sean Bright <sean.bright@gmail.com>
+
+	* term.c: Add support for extended number format terminfo files.
+
+	  ncurses 6.1 introduced an extended number format for terminfo files
+	  which the terminfo parsing in Asterisk is not able to parse. This
+	  results in some TERM values that do support color (screen-256color on
+	  Ubuntu 20.04 for example) to not get a color console.
+
+	  ASTERISK-29630 #close
+
+	  Change-Id: I27a4fcfab502219924af2d6b1c46feba92903cb3
+
+2021-09-07 12:32 +0000 [f67b72093e]  Sean Bright <sean.bright@gmail.com>
+
+	* app_voicemail.c: Ability to silence instructions if greeting is present.
+
+	  There is an option to silence voicemail instructions but it does not
+	  take into consideration if a recorded greeting exists or not. Add a
+	  new 'S' option that does that.
+
+	  ASTERISK-29632 #close
+
+	  Change-Id: I03f2f043a9beb9d99deab302247e2a8686066fb4
+
+2021-09-03 00:30 +0000 [f1e1f9f37f]  Jasper Hafkenscheid <jasper.hafkenscheid@wearespindle.com>
+
+	* res_srtp: Disable parsing of not enabled cryptos
+
+	  When compiled without extended srtp crypto suites also disable parsing
+	  these from received SDP. This prevents using these, as some client
+	  implementations are not stable.
+
+	  ASTERISK-29625
+
+	  Change-Id: I7dafb29be1cdaabdc984002573f4bea87520533a
+
+2021-09-06 11:37 +0000 [5a5ea06ffc]  Sean Bright <sean.bright@gmail.com>
+
+	* dns.c: Load IPv6 DNS resolvers if configured.
+
+	  IPv6 nameserver addresses are stored in different part of the
+	  __res_state structure, so look there if we appear to have support for
+	  it.
+
+	  ASTERISK-28004 #close
+
+	  Change-Id: I67067077d8a406ee996664518d9c8fbf11f6977d
+
+2021-09-08 07:52 +0000 [0070b9184c]  George Joseph <gjoseph@digium.com>
+
+	* bridge_softmix: Suppress error on topology change failure
+
+	  There are conditions under which a failure to change topology
+	  is expected so there's no need to print an ERROR message.
+
+	  ASTERISK-29618
+	  Reported by: Alexander
+
+	  Change-Id: Idc168b8588e018bf3a23769f08c4ad646086d481
+
+2021-08-31 02:50 +0000 [3c31b6aaa2]  sungtae kim <sungtae.kim@avoxi.com>
+
+	* resource_channels.c: Fix wrong external media parameter parse
+
+	  Fixed ARI external media handler to accept body parameters.
+
+	  ASTERISK-29622
+
+	  Change-Id: I49509c48a6cbc0fb4165bfa4f834b5e8b9ace20d
+
+2021-08-25 10:21 +0000 [16b0f460f6]  Sean Bright <sean.bright@gmail.com>
+
+	* config_options: Handle ACO arrays correctly in generated XML docs.
+
+	  There are 3 separate changes here but they are all closely related:
+
+	  * Only try to set matchfield attributes on 'field' nodes
+
+	  * We need to adjust how we treat the category pointer based on the
+	    value of the category_match, to avoid memory corruption. We now
+	    generate a regex-like string when match types other than
+	    ACO_WHITELIST and ACO_BLACKLIST are used.
+
+	  * Switch app_agent_pool from ACO_BLACKLIST_ARRAY to
+	    ACO_BLACKLIST_EXACT since we only have one category we need to
+	    ignore, not two.
+
+	  ASTERISK-29614 #close
+
+	  Change-Id: I7be7bdb1bb9814f942bc6bb4fdd0a55a7b7efe1e
+
+2021-08-18 14:44 +0000 [29770520b3]  Naveen Albert <asterisk@phreaknet.org>
+
+	* chan_iax2: Add ANI2/OLI information element
+
+	  Adds an information element for ANI2 so that
+	  Originating Line Information can be transmitted
+	  over IAX2 channels.
+
+	  ASTERISK-29605 #close
+
+	  Change-Id: Iaeacdf6ccde18eaff7f776a0f49fee87dcb549d2
+
+2021-08-31 15:03 +0000 [185321066f]  Mark Murawski <markm@intellasoft.net>
+
+	* pbx_ael:  Fix crash and lockup issue regarding 'ael reload'
+
+	  Currently pbx_ael does not check if a reload is currently pending
+	  before proceeding with a reload. This can cause multiple threads to
+	  operate at the same time on what should be mutex protected data. This
+	  change adds protection to reloading to ensure only one ael reload is
+	  executing at a time.
+
+	  ASTERISK-29609 #close
+
+	  Change-Id: I5ed392ad226f6e4e7696ad742076d3e45c57af35
+
+2021-08-25 06:49 +0000 [0e4a1c5079]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_read: Allow reading # as a digit
+
+	  Allows for the digit # to be read as a digit,
+	  just like any other DTMF digit, as opposed to
+	  forcing it to be used as an end of input
+	  indicator. The default behavior remains
+	  unchanged.
+
+	  ASTERISK-18454 #close
+
+	  Change-Id: I3033432adb9d296ad227e76b540b8b4a2417665b
+
+2021-04-05 14:06 +0000 [18189ff594]  Sebastien Duthil <sduthil@wazo.community>
+
+	* res_rtp_asterisk: Automatically refresh stunaddr from DNS
+
+	  This allows the STUN server to change its IP address without having to
+	  reload the res_rtp_asterisk module.
+
+	  The refresh of the name resolution occurs first when the module is
+	  loaded, then recurringly, slightly after the previous DNS answer TTL
+	  expires.
+
+	  ASTERISK-29508 #close
+
+	  Change-Id: I7955a046293f913ba121bbd82153b04439e3465f
+
+2021-08-24 20:04 +0000 [4301fe20d1]  Naveen Albert <asterisk@phreaknet.org>
+
+	* bridge_basic: Change warning to verbose if transfer cancelled
+
+	  The attended transfer feature will emit a warning if the user
+	  cancels the transfer or the attended transfer doesn't complete
+	  for any reason. Changes the warning to a verbose message,
+	  since nothing is actually wrong here.
+
+	  ASTERISK-29612 #close
+
+	  Change-Id: I64c93cdb21360a0a8d45e9cb6db3af8168f66e6d
+
+2021-08-20 15:35 +0000 [9e947b0463]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_queue: Don't reset queue stats on reload
+
+	  Prevents reloads of app_queue from also resetting
+	  queue statistics.
+
+	  Also preserves individual queue agent statistics
+	  if we're just reloading members.
+
+	  ASTERISK-28701
+
+	  Change-Id: Ib5d4cdec175e44de38ef0f6ede4a7701751766f1
+
+2021-08-25 09:29 +0000 [f22b413ece]  Alexander Traud <pabstraud@compuserve.com>
+
+	* dialplan: Add one static and fix two whitespace errors.
+
+	  Change-Id: Ia14d515ab63e773097adc6af772ca7123a392f83
+
+2021-08-25 09:23 +0000 [e65e1c5c6c]  Alexander Traud <pabstraud@compuserve.com>
+
+	* res_rtp_asterisk: sqrt(.) requires the header math.h.
+
+	  ASTERISK-29616
+
+	  Change-Id: I6c01623926bf10ccac32612687a50fdab3ba0900
+
+2021-06-19 23:36 +0000 [db4a3b117d]  Sarah Autumn <sarah@connectionsmuseum.org>
+
+	* sig_analog: Changes to improve electromechanical signalling compatibility
+
+	  This changeset is intended to address compatibility issues encountered
+	  when interfacing Asterisk to electromechanical telephone switches that
+	  implement ANI-B, ANI-C, or ANI-D.
+
+	  In particular the behaviours that this impacts include:
+
+	   - FGC-CAMA did not work at all when using MF signaling. Modified the
+	     switch case block to send calls to the correct part of the
+	     signaling-handling state machine.
+
+	   - For FGC-CAMA operation, the delay between called number ST and
+	     second wink for ANI spill has been made configurable; previously
+	     all calls were made to wait for one full second.
+
+	   - After the ANI spill, previous behavior was to require a 'ST' tone
+	     to advance the call.  This has been changed to allow 'STP' 'ST2P'
+	     or 'ST3P' as well, for compatibility with ANI-D.
+
+	   - Store ANI2 (ANI INFO) digits in the CALLERID(ANI2) channel variable.
+
+	   - For calls with an ANI failure, No. 1 Crossbar switches will send
+	     forward a single-digit failure code, with no calling number digits
+	     and no ST pulse to terminate the spill.  I've made the ANI timeout
+	     configurable so to reduce dead air time on calls with ANI fail.
+
+	   - ANI info digits configurable.  Modern digital switches will send 2
+	     digits, but ANI-B sends only a single info digit.  This caused the
+	     ANI reported by Asterisk to be misaligned.
+
+	   - Changed a confusing log message to be more informative.
+
+	  ASTERISK-29518
+
+	  Change-Id: Ib7e27d987aee4ed9bc3663c57ef413e21b404256
+
+2021-08-16 08:25 +0000 [a662d75556]  George Joseph <gjoseph@digium.com>
+
+	* res_pjproject: Allow mapping to Asterisk TRACE level
+
+	  Allow mapping pjproject log messages to the Asterisk TRACE
+	  log level.  The defaults were also changes to log pjproject
+	  levels 3,4 to DEBUG and 5,6 to TRACE.  Previously 3,4,5,6
+	  all went to DEBUG.
+
+	  ASTERISK-29582
+
+	  Change-Id: I859a37a8dec263ed68099709cfbd3e665324c72d
+
+2021-08-05 11:55 +0000 [2451dfd89f]  Andre Barbosa <andre.emanuel.barbosa@gmail.com>
+
+	* media_cache: Don't lock when curl the remote file
+
+	  When playing a remote sound file, which is not in cache, first we need
+	  to download it with ast_bucket_file_retrieve.
+
+	  This can take a while if the remote host is slow. The current CURL
+	  timeout is 180secs, so in extreme situations, it can take 3 minutes to
+	  return.
+
+	  Because ast_media_cache_retrieve has a lock on all function, while we
+	  are waiting for the delayed download, Asterisk is not able to play any
+	  more files, even the files already cached locally.
+
+	  ASTERISK-29544 #close
+
+	  Change-Id: I8d4142b463ae4a1d4c41bff2bf63324821567408
+
+2021-08-04 14:16 +0000 [e01a6c026d]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_scramble: Audio scrambler function
+
+	  Adds a function to scramble audio on a channel using
+	  whole spectrum frequency inversion. This can be used
+	  as a privacy enhancement with applications like
+	  ChanSpy or other potentially sensitive audio.
+
+	  ASTERISK-29542
+
+	  Change-Id: I01020769d91060a1f56a708eb405f87648d1a67e
+
+2021-06-28 09:25 +0000 [d6034df64a]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_math: Return integer instead of float if possible
+
+	  The MIN, MAX, and ABS functions all support float
+	  arguments, but currently return floats even if the
+	  arguments are all integers and the response is
+	  a whole number, in which case the user is likely
+	  expecting an integer. This casts the float to an integer
+	  before printing into the response buffer if possible.
+
+	  ASTERISK-29495
+
+	  Change-Id: I902d29eacf3ecd0f8a6a5e433c97f0421d205488
+
+2021-08-04 09:46 +0000 [b5044586f7]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_morsecode: Add American Morse code
+
+	  Previously, the Morsecode application only supported international
+	  Morse code. This adds support for American Morse code and adds an
+	  option to configure the frequency used in off intervals.
+
+	  Additionally, the application checks for hangup between tones
+	  to prevent application execution from continuing after hangup.
+
+	  ASTERISK-29541
+
+	  Change-Id: I172431a2e18e6527d577e74adfb05b154cba7bd4
+
+2021-08-12 16:02 +0000 [3f9ef427b5]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_milliwatt: Timing fix
+
+	  The Milliwatt application uses incorrect tone timings
+	  that cause it to play the 1004 Hz tone constantly.
+
+	  This adds an option to enable the correct timing
+	  behavior, so that the Milliwatt application can
+	  be used for milliwatt test lines. The default behavior
+	  remains unchanged for compatability reasons, even
+	  though it is incorrect.
+
+	  ASTERISK-29575 #close
+
+	  Change-Id: I73ccc6c6fcaa31931c6fff3b85ad1805b2ce9d8c
+
+2021-08-04 19:28 +0000 [2394757e55]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_originate: Add ability to set codecs
+
+	  A list of codecs to use for dialplan-originated calls can
+	  now be specified in Originate, similar to the ability
+	  in call files and the manager action.
+
+	  Additionally, we now default to just using the slin codec
+	  for originated calls, rather than all the slin* codecs up
+	  through slin192, which has been known to cause issues
+	  and inconsistencies from AMI and call file behavior.
+
+	  ASTERISK-29543
+
+	  Change-Id: I96a1aeb83d54b635b7a51e1b4680f03791622883
+
+2021-08-16 11:11 +0000 [73e2288db7]  Alexander Traud <pabstraud@compuserve.com>
+
+	* BuildSystem: Remove two dead exceptions for compiler Clang.
+
+	  Commit 305ce3d added -Wno-parentheses-equality to Makefile.rules,
+	  turning the previous two warning suppressions from commit e9520db
+	  redundant. Let us remove the latter.
+
+	  Change-Id: I0b471254b31e6e05902062761dded4b3e626c7ac
+
+2021-08-16 14:31 +0000 [432fe9dc2a]  Naveen Albert <asterisk@phreaknet.org>
+
+	* chan_alsa, chan_sip: Add replacement to moduleinfo
+
+	  Adds replacement modules to the moduleinfo for
+	  chan_alsa and chan_sip.
+
+	  ASTERISK-29601 #close
+
+	  Change-Id: I7a4877b0d5c0c17e088e8fa8ebbfa9a195223cbc
+
+2021-08-17 08:11 +0000 [ecf699c325]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_monitor: Disable building by default.
+
+	  ASTERISK-29602
+
+	  Change-Id: I6f0af0a959409cdbc6b185b1604301bafc872a5a
+
+2021-08-16 13:47 +0000 [daca793ad4]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* muted: Remove deprecated application.
+
+	  ASTERISK-29600
+
+	  Change-Id: I0ae1c6a2996da43217126f094de90761314dcf82
+
+2021-08-16 13:39 +0000 [650cf0b444]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* conf2ael: Remove deprecated application.
+
+	  ASTERISK-29599
+
+	  Change-Id: I75dc77162926fb17e7c6caf8f04e3aabd792fb0c
+
+2021-08-16 13:26 +0000 [368aa47962]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_config_sqlite: Remove deprecated module.
+
+	  ASTERISK-29598
+
+	  Change-Id: I8ef17023f55bf01f2e309b06f4778a8ca7252c91
+
+2021-08-16 13:22 +0000 [9d5f55a5f3]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* chan_vpb: Remove deprecated module.
+
+	  ASTERISK-29597
+
+	  Change-Id: I19bb39eed0257ddfef453eb2df5646d073d50fe1
+
+2021-08-16 13:18 +0000 [72a2140a50]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* chan_misdn: Remove deprecated module.
+
+	  ASTERISK-29596
+
+	  Change-Id: Ibae9490c1b35cadbf7028d24610f745277c8535e
+
+2021-08-16 13:13 +0000 [7b0d3d3550]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* chan_nbs: Remove deprecated module.
+
+	  ASTERISK-29595
+
+	  Change-Id: Ib5c7d43a780f2fb94cee90738e4c1af211ae4a33
+
+2021-08-16 13:10 +0000 [7361a52820]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* chan_phone: Remove deprecated module.
+
+	  ASTERISK-29594
+
+	  Change-Id: I79a9961cb5062fadbccb0ea93f087bdd32685316
+
+2021-08-16 13:06 +0000 [d0ad32c7cf]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* chan_oss: Remove deprecated module.
+
+	  ASTERISK-29593
+
+	  Change-Id: Ib53a42ad974c63871344b95078c61c188e43da99
+
+2021-08-16 13:04 +0000 [e4b6f24a1d]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* cdr_syslog: Remove deprecated module.
+
+	  ASTERISK-29592
+
+	  Change-Id: Ic8eb6a2100ad5bc3b48338a6d0a6cfa70ecbc50f
+
+2021-08-16 12:56 +0000 [f18107f191]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_dahdiras: Remove deprecated module.
+
+	  ASTERISK-29591
+
+	  Change-Id: I021d37b729631d40f84e35bb21e2893777be1858
+
+2021-08-16 12:55 +0000 [b1e5b1874c]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_nbscat: Remove deprecated module.
+
+	  ASTERISK-29590
+
+	  Change-Id: I87cf0f536b77d222c8eda003376ac47fae86ed43
+
+2021-08-16 12:52 +0000 [7ee6fb0372]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_image: Remove deprecated module.
+
+	  ASTERISK-29589
+
+	  Change-Id: I8057eb2ca1ca4c3b27ed2fe04bea10e9cb551cdd
+
+2021-08-16 12:50 +0000 [0b3a149001]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_url: Remove deprecated module.
+
+	  ASTERISK-29588
+
+	  Change-Id: If846d40b37c5b646bcd7326111db280529a5971b
+
+2021-08-16 12:48 +0000 [41afcb9422]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_fax: Remove deprecated module.
+
+	  ASTERISK-29587
+
+	  Change-Id: I038237bbb56b1161d7d5e20cda11ed32e13d3ca2
+
+2021-08-16 12:46 +0000 [83cad340fc]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_ices: Remove deprecated module.
+
+	  ASTERISK-29586
+
+	  Change-Id: I1e0a4535135b00938b609fe0ccba9bbddbac93ad
+
+2021-08-16 12:43 +0000 [1961a1b83e]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_mysql: Remove deprecated module.
+
+	  ASTERISK-29585
+
+	  Change-Id: I262930d0387d043f2a3345e8a977b314528059bf
+
+2021-08-16 12:39 +0000 [3e07b1ff62]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* cdr_mysql: Remove deprecated module.
+
+	  ASTERISK-29584
+
+	  Change-Id: I4bd3695d089121f810d692a82361d39d2f97ae39
+
+2021-08-10 12:41 +0000 [41ed46f474]  Sean Bright <sean.bright@gmail.com>
+
+	* mgcp: Remove dead debug code
+
+	  ASTERISK-20339 #close
+
+	  Change-Id: I36f364aaa1971241d8f3ea1a5909b463d185a2d5
+
+2021-08-11 06:15 +0000 [141dc519b0]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* policy: Deprecate modules and add versions to others.
+
+	  app_meetme is deprecated in 19, to be removed in 21.
+	  app_osplookup is deprecated in 19, to be removed in 21.
+	  chan_alsa is deprecated in 19, to be removed in 21.
+	  chan_mgcp is deprecated in 19, to be removed in 21.
+	  chan_skinny is deprecated in 19, to be removed in 21.
+	  res_pktccops is deprecated in 19, to be removed in 21.
+	  app_macro was deprecated in 16, to be removed in 21.
+	  chan_sip was deprecated in 17, to be removed in 21.
+	  res_monitor was deprecated in 16, to be removed in 21.
+
+	  ASTERISK-29548
+	  ASTERISK-29549
+	  ASTERISK-29550
+	  ASTERISK-29551
+	  ASTERISK-29552
+	  ASTERISK-29553
+	  ASTERISK-29558
+	  ASTERISK-29567
+	  ASTERISK-29572
+
+	  Change-Id: Ic3bee31a10d42c4b3bbc913d893f7b2a28a27131
+
+2021-06-16 15:30 +0000 [7383f74dfc]  Naveen Albert <asterisk@phreaknet.org>
+
+	* func_frame_drop: New function
+
+	  Adds function to selectively drop specified frames
+	  in the TX or RX direction on a channel, including
+	  control frames.
+
+	  ASTERISK-29478
+
+	  Change-Id: I8147c9d55d74e2e48861edba6b22f930920541ec
+
+2021-08-02 12:33 +0000 [835ab50724]  Alexander Traud <pabstraud@compuserve.com>
+
+	* aelparse: Accept an included context with timings.
+
+	  With Asterisk 1.6.0, in the main parser for the configuration file
+	  extensions.conf, the separator was changed from vertical bar to comma.
+	  However, the first separator was not changed in aelparse; it still had
+	  to be a vertical bar, and no comma was allowed.
+
+	  Additionally, this change allows the vertical bar for the first and
+	  last parameter again, even in the main parser, because the vertical bar
+	  was still accepted for the other parameters.
+
+	  ASTERISK-29540
+
+	  Change-Id: I882e17c73adf4bf2f20f9046390860d04a9f8d81
+
+2021-08-03 11:30 +0000 [37f7d19c8c]  Kevin Harwell <kharwell@sangoma.com>
+
+	* format_ogg_speex: Implement a "not supported" write handler
+
+	  This format did not specify a "write" handler, so when attempting to write
+	  to it (ast_writestream) a crash would occur.
+
+	  This patch adds a default handler that simply issues a "not supported"
+	  warning, thus no longer crashing.
+
+	  ASTERISK-29539
+
+	  Change-Id: I8f6ddc7cc3b15da30803be3b1cf68e2ba0fbce91
+
+2021-06-28 08:48 +0000 [4c49c84dee]  Naveen Albert <asterisk@phreaknet.org>
+
+	* cdr_adaptive_odbc: Prevent filter warnings
+
+	  Previously, if CDR filters were used so that
+	  not all CDR records used all sections defined
+	  in cdr_adaptive_odbc.conf, then warnings will
+	  always be emitted (if each CDR record is unique
+	  to a particular section, n-1 warnings to be
+	  specific).
+
+	  This turns the offending warning log into
+	  a verbose message like the other one, since
+	  this behavior is intentional and not
+	  indicative of anything wrong.
+
+	  ASTERISK-29494
+
+	  Change-Id: Ifd314fa9298722bc99494d5ca2658a5caa94a5f8
+
+2021-07-25 16:53 +0000 [0975cff6c0]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_queue: Allow streaming multiple announcement files
+
+	  Allows multiple files comprising an agent announcement
+	  to be played by separating on the ampersand, similar
+	  to the multi-file support in other Asterisk applications.
+
+	  ASTERISK-29528
+
+	  Change-Id: Iec600d8cd5ba14aa1e4e37f906accb356cd7891a
+
+2021-04-13 02:36 +0000 [ac958b0f50]  Igor Goncharovsky <igorg@iqtek.ru>
+
+	* res_pjsip_header_funcs: Add PJSIP_HEADERS() ability to read header by pattern
+
+	  PJSIP currently does not provide a function to replace SIP_HEADERS() function to get a list of headers from INVITE request.
+	  It may be used to get all X- headers in case the actual set and names of headers unknown.
+
+	  ASTERISK-29389
+
+	  Change-Id: Ic09d395de71a0021e0d6c5c29e1e19d689079f8b
+
+2021-07-08 07:34 +0000 [f13eef719c]  Rijnhard Hessel <rijnhard@teleforge.co.za>
+
+	* res_statsd: handle non-standard meter type safely
+
+	  Meter types are not well supported,
+	  lacking support in telegraf, datadog and the official statsd servers.
+	  We deprecate meters and provide a compliant fallback for any existing usages.
+
+	  A flag has been introduced to allow meters to fallback to counters.
+
+
+	  ASTERISK-29513
+
+	  Change-Id: I5fcb385983a1b88f03696ff30a26b55c546a1dd7
+
+2021-07-23 11:00 +0000 [382143e58e]  Sean Bright <sean.bright@gmail.com>
+
+	* res_http_media_cache: Cleanup audio format lookup in HTTP requests
+
+	  Asterisk first looks at the end of the URL to determine the file
+	  extension of the returned audio, which in many cases will not work
+	  because the URL may end with a query string or a URL fragment. If that
+	  fails, Asterisk then looks at the Content-Type header and then finally
+	  parses the URL to get the extension.
+
+	  The order has been changed such that we look at the Content-Type
+	  header first, followed by looking for the extension of the parsed
+	  URL. We no longer look at the end of the URL, which was error prone.
+
+	  ASTERISK-29527 #close
+
+	  Change-Id: I1e3f83b339ef2b80661704717c23568536511032
+
+2021-07-22 11:39 +0000 [ff8ca2c9f1]  under <pcapdump@gmail.com>
+
+	* codec_builtin.c: G729 audio gets corrupted by Asterisk due to smoother
+
+	  If Asterisk gets G.729 6-byte VAD frames inbound, then at outbound Asterisk sends this G.729 stream with non-continuous timestamps.
+	  This makes the audio stream not-playable at the receiver side.
+	  Linphone isn't able to play such an audio - lots of disruptions are heard.
+	  Also I had complains of bad audio from users which use other types of phones.
+
+	  After debugging, I found this is a regression connected with RTP Smoother (main/smoother.c).
+
+	  Smoother has a special code to handle G.729 VAD frames (search for AST_SMOOTHER_FLAG_G729 in smoother.c).
+
+	  However, this flag is never set in Asterisk-12 and newer.
+	  Previously it has been set (see Asterisk-11).
+
+	  ASTERISK-29526 #close
+
+	  Change-Id: I6f51ecb1a3ecd9c6d59ec5a6811a27446e17065d
+
+2021-06-16 15:26 +0000 [6645cf8d45]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_dtmfstore: New application to store digits
+
+	  Adds application to asynchronously collect digits
+	  dialed on a channel in the TX or RX direction
+	  using a framehook and stores them in a specified
+	  variable, up to a configurable number of digits.
+
+	  ASTERISK-29477
+
+	  Change-Id: I51aa93fc9507f7636ac44806c4420ce690423e6f
+
+2021-07-27 07:53 +0000 [90c9c90b11]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* docs: Remove embedded macro in WaitForCond XML documentation.
+
+	  Change-Id: I40c6514e1843e320f3cbe0b2c70d4a98c0e35b9c
+
+2021-05-10 17:59 +0000 [56f9c28a50]  Kevin Harwell <kharwell@sangoma.com>
+
+	* AST-2021-008 - chan_iax2: remote crash on unsupported media format
+
+	  If chan_iax2 received a packet with an unsupported media format, for
+	  example vp9, then it would set the frame's format to NULL. This could
+	  then result in a crash later when an attempt was made to access the
+	  format.
+
+	  This patch makes it so chan_iax2 now ignores/drops frames received
+	  with unsupported media format types.
+
+	  ASTERISK-29392 #close
+
+	  Change-Id: Ifa869a90dafe33eed8fd9463574fe6f1c0ad3eb1
+	  (cherry picked from commit 2a141a58b61ba0ed91061e1acc2c1955e0160f73)
+
+2021-04-28 07:36 +0000 [45af7e9984]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* AST-2021-007 - res_pjsip_session: Don't offer if no channel exists.
+
+	  If a re-INVITE is received after we have sent a BYE request then it
+	  is possible for no channel to be present on the session. If this
+	  occurs we allow PJSIP to produce the offer instead. Since the call
+	  is being hung up if it produces an incorrect offer it doesn't
+	  actually matter. This also ensures that code which produces SDP
+	  does not need to handle if a channel is not present.
+
+	  ASTERISK-29381
+
+	  Change-Id: I673cb88c432f38f69b2e0851d55cc57a62236042
+	  (cherry picked from commit 523a79528932e63c6aaad2fffb3fa08427f8f920)
+
+2021-06-14 13:28 +0000 [151bdbc658]  Kevin Harwell <kharwell@sangoma.com>
+
+	* AST-2021-009 - pjproject-bundled: Avoid crash during handshake for TLS
+
+	  If an SSL socket parent/listener was destroyed during the handshake,
+	  depending on timing, it was possible for the handling callback to
+	  attempt access of it after the fact thus causing a crash.
+
+	  ASTERISK-29415 #close
+
+	  Change-Id: I105dacdcd130ea7fdd4cf2010ccf35b5eaf1432d
+	  (cherry picked from commit 3025ef4f6e79730d35c4514bf9c6dc4be87fa532)
+
+2021-07-21 10:20 +0000 [0ac346ec47]  Ben Ford <bford@digium.com>
+
+	* Update default branch for Asterisk 19.
+
+	  Changed default branch to correct version for Asterisk 19.
+
+	  Change-Id: I6244c8ac14b9c0eb8bdf07fe58db24dc95cb1086
+
+2021-06-29 11:07 +0000 [f4d3f021f9]  Andre Barbosa <andre.emanuel.barbosa@gmail.com>
+
+	* res_stasis_playback: Check for chan hangup on play_on_channels
+
+	  Verify `ast_check_hangup` before looping to the next sound file.
+	  If the call is already hangup we just break the cycle.
+	  It also ensures that the PlaybackFinished event is sent if the call was hangup.
+
+	  This is also use-full when we are playing a big list of file for a channel that is hangup.
+	  Before this patch Asterisk will give a warning for every sound not played and fire a PlaybackStart for every sound file on the list tried to be played.
+
+	  With the patch we just break the playback cycle when the chan is hangup.
+
+	  ASTERISK-29501 #close
+
+	  Change-Id: Ic4e1c01b974c9a1f2d9678c9d6b380bcfc69feb8
+
+2021-07-02 10:15 +0000 [d5bb27a06f]  Sean Bright <sean.bright@gmail.com>
+
+	* res_http_media_cache.c: Fix merge errors from 18 -> master
+
+	  ASTERISK-27871 #close
+
+	  Change-Id: I6624f2d3a57f76a89bb372ef54a124929a0338d7
+
+2021-07-15 15:04 +0000 [237285a9a8]  Sean Bright <sean.bright@gmail.com>
+
+	* res_pjsip_stir_shaken: RFC 8225 compliance and error message cleanup.
+
+	  From RFC 8225 Section 5.2.1:
+
+	      The "dest" claim is a JSON object with the claim name of "dest"
+	      and MUST have at least one identity claim object.  The "dest"
+	      claim value is an array containing one or more identity claim JSON
+	      objects representing the destination identities of any type
+	      (currently "tn" or "uri").  If the "dest" claim value array
+	      contains both "tn" and "uri" claim names, the JSON object should
+	      list the "tn" array first and the "uri" array second.  Within the
+	      "tn" and "uri" arrays, the identity strings should be put in
+	      lexicographical order, including the scheme-specific portion of
+	      the URI characters.
+
+	  Additionally, make it clear that there was a failure to sign the JWT
+	  payload and not necessarily a memory allocation failure.
+
+	  Change-Id: Ia8733b861aef6edfaa9c2136e97b447a01578dc9
+
+2021-07-02 10:15 +0000 [d568326807]  Sean Bright <sean.bright@gmail.com>
+
+	* res_http_media_cache.c: Parse media URLs to find extensions.
+
+	  Use cURL's URL parsing API, falling back to the urlparser library, to
+	  parse playback URLs in order to find their file extensions.
+
+	  For backwards compatibility, we first look at the full URL, then at
+	  any Content-Type header, and finally at just the path portion of the
+	  URL.
+
+	  ASTERISK-27871 #close
+
+	  Change-Id: I16d0682f6d794be96539261b3e48f237909139cb
+
+2021-07-13 10:31 +0000 [785e4afc20]  Sean Bright <sean.bright@gmail.com>
+
+	* main/cdr.c: Correct Party A selection.
+
+	  This appears to just have been a copy/paste error from 6258bbe7. Fix
+	  suggested by Ross Beer in ASTERISK~29166.
+
+	  Change-Id: I51e0de92042e53f37597c6f83a75621ef0d1ae37
+
+2021-06-30 17:15 +0000 [8a21d466ea]  Sebastien Duthil <sduthil@wazo.community>
+
+	* stun: Emit warning message when STUN request times out
+
+	  Without this message, it is not obvious that the reason is STUN timeout.
+
+	  ASTERISK-29507 #close
+
+	  Change-Id: I26e4853c23a1aed324552e1b9683ea3c05cb1f74
+
+2021-05-26 12:09 +0000 [244491f9b2]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_reload: New Reload application
+
+	  Adds an application to reload modules
+	  from within the dialplan.
+
+	  ASTERISK-29454
+
+	  Change-Id: Ic8ab025d8b38dd525b872b41c465c999c5810774
+
+2021-07-08 09:32 +0000 [99d44f0c5a]  Igor Goncharovsky <igorg@iqtek.ru>
+
+	* res_ari: Fix audiosocket segfault
+
+	  Add check that data parameter specified when audiosocket used for externalMedia.
+
+	  ASTERISK-29514 #close
+
+	  Change-Id: Ie562f03c5d6c3835a3631f376b3d43e75b8f9617
+
+2021-06-30 08:07 +0000 [0ac9c83561]  Sean Bright <sean.bright@gmail.com>
+
+	* res_pjsip_config_wizard.c: Add port matching support.
+
+	  In f8b0c2c9 we added support for port numbers in 'match' statements
+	  but neglected to include that support in the PJSIP config wizard.
+
+	  The removed code would have also prevented IPv6 addresses from being
+	  successfully used in the config wizard as well.
+
+	  ASTERISK-29503 #close
+
+	  Change-Id: Idd5bbfd48009e7a741757743dbaea68e2835a34d
+
+2021-05-22 09:31 +0000 [c01b4e0d4b]  Naveen Albert <mail@interlinked.x10host.com>
+
+	* app_waitforcond: New application
+
+	  While several applications exist to wait for
+	  a certain event to occur, none allow waiting
+	  for any generic expression to become true.
+	  This application allows for waiting for a condition
+	  to become true, with configurable timeout and
+	  checking interval.
+
+	  ASTERISK-29444
+
+	  Change-Id: I08adf2824b8bc63405778cf355963b5005612f41
+
+2021-06-04 06:11 +0000 [a47308ccb2]  Andre Barbosa <andre.emanuel.barbosa@gmail.com>
+
+	* res_stasis_playback: Send PlaybackFinish event only once for errors
+
+	  When we try to play a list of sound files in the same Play command,
+	  we get only one PlaybackFinish event, after all sounds are played.
+
+	  But in the case where the Play fails (because channel is destroyed
+	  for example), Asterisk will send one PlaybackFinish event for each
+	  sound file still to be played. If the list is big, Asterisk is
+	  sending many events.
+
+	  This patch adds a failed state so we can understand that the play
+	  failed. On that case we don't send the event, if we still have a
+	  list of sounds to be played.
+
+	  When we reach the last sound, we send the PlaybackFinish with
+	  the failed state.
+
+	  ASTERISK-29464 #close
+
+	  Change-Id: I4c2e5921cc597702513af0d7c6c2c982e1798322
+
+2021-06-17 07:57 +0000 [bc973bd719]  George Joseph <gjoseph@digium.com>
+
+	* jitterbuffer:  Correct signed/unsigned mismatch causing assert
+
+	  If the system time has stepped backwards because of a time
+	  adjustment between the time a frame is timestamped and the
+	  time we check the timestamps in abstract_jb:hook_event_cb(),
+	  we get a negative interval, but we don't check for that there.
+	  abstract_jb:hook_event_cb() then calls
+	  fixedjitterbuffer:fixed_jb_get() (via abstract_jb:jb_get_fixed)
+	  and the first thing that does is assert(interval >= 0).
+
+	  There are several issues with this...
+
+	   * abstract_jb:hook_event_cb() saves the interval in a variable
+	     named "now" which is confusing in itself.
+
+	   * "now" is defined as an unsigned int which converts the negative
+	     value returned from ast_tvdiff_ms() to a large positive value.
+
+	   * fixed_jb_get()'s parameter is defined as a signed int so the
+	     interval gets converted back to a negative value.
+
+	   * fixed_jb_get()'s assert is NOT an ast_assert but a direct define
+	     that points to the system assert() so it triggers even in
+	     production mode.
+
+	  So...
+
+	   * hook_event_cb()'s "now" was renamed to "relative_frame_start" and
+	     changed to an int64_t.
+	   * hook_event_cb() now checks for a negative value right after
+	     retrieving both the current and framedata timestamps and just
+	     returns the frame if the difference is negative.
+	   * fixed_jb_get()'s local define of ASSERT() was changed to call
+	     ast_assert() instead of the system assert().
+
+	  ASTERISK-29480
+	  Reported by: Dan Cropp
+
+	  Change-Id: Ic469dec73c2edc3ba134cda6721a999a9714f3c9
+
+2021-05-21 19:08 +0000 [1e5a2cfe30]  Naveen Albert <mail@interlinked.x10host.com>
+
+	* app_dial: Expanded A option to add caller announcement
+
+	  Hitherto, the A option has made it possible to play
+	  audio upon answer to the called party only. This option
+	  is expanded to allow for playback of an audio file to
+	  the caller instead of or in addition to the audio
+	  played to the answerer.
+
+	  ASTERISK-29442
+
+	  Change-Id: If6eed3ff5c341dc8c588c8210987f2571e891e5e
+
+2021-06-21 06:31 +0000 [5382b9dbb8]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* core: Don't play silence for Busy() and Congestion() applications.
+
+	  When using the Busy() and Congestion() applications the
+	  function ast_safe_sleep is used by wait_for_hangup to safely
+	  wait on the channel. This function may send silence if Asterisk
+	  is configured to do so using the transmit_silence option.
+
+	  In a scenario where an answered channel dials a Local channel
+	  either directly or through call forwarding and the Busy()
+	  or Congestion() dialplan applications were executed with the
+	  transmit_silence option enabled the busy or congestion
+	  tone would not be heard.
+
+	  This is because inband generation of tones (such as busy
+	  and congestion) is stopped when other audio is sent to
+	  the channel they are being played to. In the given
+	  scenario the transmit_silence option would result in
+	  silence being sent to the channel, thus stopping the
+	  inband generation.
+
+	  This change adds a variant of ast_safe_sleep which can be
+	  used when silence should not be played to the channel. The
+	  wait_for_hangup function has been updated to use this
+	  resulting in the tones being generated as expected.
+
+	  ASTERISK-29485
+
+	  Change-Id: I066bfc987a3ad6f0ccc88e0af4cd63f6a4729133
+
+2021-05-07 01:18 +0000 [c30f68a57b]  Bernd Zobl <b.zobl@commend.com>
+
+	* res_pjsip_sdp_rtp: Evaluate remotely held for Session Progress
+
+	  With the fix for ASTERISK_28754 channels are no longer put on hold if an
+	  outbound INVITE is answered with a "Session Progress" containing
+	  "inactive" audio.
+
+	  The previous change moved the evaluation of the media attributes to
+	  `negotiate_incoming_sdp_stream()` to have the `remotely_held` status
+	  available when building the SDP in `create_outgoing_sdp_stream()`.
+	  This however means that an answer to an outbound INVITE, which does not
+	  traverse `negotiate_incoming_sdp_stream()`, cannot set the
+	  `remotely_held` status anymore.
+
+	  This change moves the check so that both, `negotiate_incoming_sdp_stream()` and
+	  `apply_negotiated_sdp_stream()` can do the checks.
+
+	  ASTERISK-29479
+
+	  Change-Id: Icde805a819399d5123b688e1ed1d2bcd9d5b0f75
+
+2021-06-16 08:50 +0000 [b7027de195]  George Joseph <gjoseph@digium.com>
+
+	* res_pjsip_messaging: Overwrite user in existing contact URI
+
+	  When the MessageSend destination is in the form
+	  PJSIP/<number>@<endpoint> and the endpoint's contact
+	  URI already has a user component, that user component
+	  will now be replaced with <number> when creating the
+	  request URI.
+
+	  ASTERISK_29404
+
+	  Change-Id: I80e5910fa25c803d1440da0594a0d6b34b6b4ad5
+
+2021-03-16 11:45 +0000 [f160725fc4]  Bernd Zobl <b.zobl@commend.com>
+
+	* res_pjsip/pjsip_message_filter: set preferred transport in pjsip_message_filter
+
+	  Set preferred transport when querying the local address to use in
+	  filter_on_tx_messages(). This prevents the module to erroneously select
+	  the wrong transport if more than one transports of the same type (TCP or
+	  TLS) are configured.
+
+	  ASTERISK-29241
+
+	  Change-Id: I598e60257a7f92b29efce1fb3e9a2fc06f1439b6
+
+2021-06-10 09:34 +0000 [f812c57477]  Naveen Albert <asterisk@phreaknet.org>
+
+	* pbx_builtins: Corrects SayNumber warning
+
+	  Previously, SayNumber always emitted a warning if the caller hung up
+	  during execution. Usually this isn't correct, so check if the channel
+	  hung up and, if so, don't emit a warning.
+
+	  ASTERISK-29475
+
+	  Change-Id: Ieea4a67301c6ea83bbc7690c1d4808d79a704594
+
+2021-05-22 07:29 +0000 [56c2cc474b]  Jaco Kroon <jaco@uls.co.za>
+
+	* func_lock: Add "dialplan locks show" cli command.
+
+	  For example:
+
+	  arthur*CLI> dialplan locks show
+	  func_lock locks:
+	  Name                                     Requesters Owner
+	  uls-autoref                              0          (unlocked)
+	  1 total locks listed.
+
+	  Obviously other potentially useful stats could be added (eg, how many
+	  times there was contention, how many times it failed etc ... but that
+	  would require keeping the stats and I'm not convinced that's worth the
+	  effort.  This was useful to troubleshoot some other issues so submitting
+	  it.
+
+	  Change-Id: Ib875e56feb49d523300aec5f36c635ed74843a9f
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+2021-05-22 07:53 +0000 [19a8383a1f]  Jaco Kroon <jaco@uls.co.za>
+
+	* func_lock: Prevent module unloading in-use module.
+
+	  The scenario where a channel still has an associated datastore we
+	  cannot unload since there is a function pointer to the destroy and fixup
+	  functions in play.  Thus increase the module ref count whenever we
+	  allocate a datastore, and decrease it during destroy.
+
+	  In order to tighten the race that still exists in spite of this (below)
+	  add some extra failure cases to prevent allocations in these cases.
+
+	  Race:
+
+	  If module ref is zero, an LOCK or TRYLOCK is invoked (near)
+	  simultaneously on a channel that has NOT PREVIOUSLY taken a lock, and if
+	  in such a case the datastore is created *prior* to unloading being set
+	  to true (first step in module unload) then it's possible that the module
+	  will unload with the destructor being called (and segfault) post the
+	  module being unloaded.  The module will however wait for such locks to
+	  release prior to unloading.
+
+	  If post that we can recheck the module ref before returning the we can
+	  (in theory, I think) eliminate the last of the race.  This race is
+	  mostly theoretical in nature.
+
+	  Change-Id: I21a514a0b56755c578a687f4867eacb8b59e23cf
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+2021-05-22 07:42 +0000 [e8875d5ca1]  Jaco Kroon <jaco@uls.co.za>
+
+	* func_lock: Fix memory corruption during unload.
+
+	  AST_TRAVERSE accessess current as current = current->(field).next ...
+	  and since we free current (and ast_free poisons the memory) we either
+	  end up on a ast_mutex_lock to a non-existing lock that can never be
+	  obtained, or a segfault.
+
+	  Incidentally add logging in the "we have to wait for a lock to release"
+	  case, and remove an ineffective statement that sets memory that was just
+	  cleared by ast_calloc to zero.
+
+	  Change-Id: Id19ba3d9867b23d0e6783b97e6ecd8e62698b8c3
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+2021-05-22 07:48 +0000 [caceba7988]  Jaco Kroon <jaco@uls.co.za>
+
+	* func_lock: Fix requesters counter in error paths.
+
+	  In two places we bail out with failure after we've already incremented
+	  the requesters counter, if this occured then it would effectively result
+	  in unload to wait indefinitely, thus preventing clean shutdown.
+
+	  Change-Id: I362a6c0dc424f736d4a9c733d818e72d19675283
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+2021-05-25 10:36 +0000 [b742514553]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_originate: Allow setting Caller ID and variables
+
+	  Caller ID can now be set on the called channel and
+	  Variables can now be set on the destination
+	  using the Originate application, just as
+	  they can be currently using call files
+	  or the Manager Action.
+
+	  ASTERISK-29450
+
+	  Change-Id: Ia64cfe97d2792bcbf4775b3126cad662922a8b66
+
+2021-06-10 16:24 +0000 [c0fc8adbb6]  Sean Bright <sean.bright@gmail.com>
+
+	* menuselect: Fix description of several modules.
+
+	  The text description needs to be the last thing on the AST_MODULE_INFO
+	  line to be pulled in properly by menuselect.
+
+	  Change-Id: I0c913e36fea8b661f42e56920b6c5513ae8fd832
+
+2021-05-23 19:20 +0000 [35437879e5]  Naveen Albert <asterisk@phreaknet.org>
+
+	* app_confbridge: New ConfKick() application
+
+	  Adds a new ConfKick() application, which may
+	  be used to kick a specific channel, all channels,
+	  or all non-admin channels from a specified
+	  conference bridge, similar to existing CLI and
+	  AMI commands.
+
+	  ASTERISK-29446
+
+	  Change-Id: I5d96b683880bfdd27b2ab1c3f2e897c5046ded9b
+
+2021-06-02 08:25 +0000 [1b38e89734]  Naveen Albert <asterisk@phreaknet.org>
+
+	* res_pjsip_dtmf_info: Hook flash
+
+	  Adds hook flash recognition support
+	  for application/hook-flash.
+
+	  ASTERISK-29460
+
+	  Change-Id: I1d060fa89a7cf41244c98f892fff44eb1c9738ea
+
+2021-05-20 09:51 +0000 [5f8cabc232]  Naveen Albert <mail@interlinked.x10host.com>
+
+	* app_confbridge: New option to prevent answer supervision
+
+	  A new user option, answer_channel, adds the capability to
+	  prevent answering the channel if it hasn't already been
+	  answered yet.
+
+	  ASTERISK-29440
+
+	  Change-Id: I26642729d0345f178c7b8045506605c8402de54b
+
+2021-06-02 08:11 +0000 [c8bf8a54c2]  Naveen Albert <asterisk@phreaknet.org>
+
+	* sip_to_pjsip: Fix missing cases
+
+	  Adds the "auto" case which is valid with
+	  both chan_sip dtmfmode and chan_pjsip's
+	  dtmf_mode, adds subscribecontext to
+	  subscribe_context conversion, and accounts
+	  for cipher = ALL being invalid.
+
+	  ASTERISK-29459
+
+	  Change-Id: Ie27d6606efad3591038000e5f3c34fa94730f6f2
+
+2021-04-22 13:07 +0000 [c3654a9959]  George Joseph <gjoseph@digium.com>
+
+	* res_pjsip_messaging: Refactor outgoing URI processing
+
+	   * Implemented the new "to" parameter of the MessageSend()
+	     dialplan application.  This allows a user to specify
+	     a complete SIP "To" header separate from the Request URI.
+
+	   * Completely refactored the get_outbound_endpoint() function
+	     to actually handle all the destination combinations that
+	     we advertized as supporting.
+
+	   * We now also accept a destination in the same format
+	     as Dial()...  PJSIP/number@endpoint
+
+	   * Added lots of debugging.
+
+	  ASTERISK-29404
+	  Reported by Brian J. Murrell
+
+	  Change-Id: I67a485196d9199916468f7f98bfb9a0b993a4cce
+
+2021-05-16 10:21 +0000 [eeffad1b62]  Naveen Albert <mail@interlinked.x10host.com>
+
+	* func_math: Three new dialplan functions
+
+	  Introduces three new dialplan functions, MIN and MAX,
+	  which can be used to calculate the minimum or
+	  maximum of up to two numbers, and ABS, an absolute
+	  value function.
+
+	  ASTERISK-29431
+
+	  Change-Id: I2bda9269d18f9d54833c85e48e41fce0e0ce4d8d
+
+2021-05-19 13:45 +0000 [12e8600849]  Ben Ford <bford@digium.com>
+
+	* STIR/SHAKEN: Add Date header, dest->tn, and URL checking.
+
+	  STIR/SHAKEN requires a Date header alongside the Identity header, so
+	  that has been added. Still on the outgoing side, we were missing the
+	  dest->tn section of the JSON payload, so that has been added as well.
+	  Moving to the incoming side, URL checking has been added to the public
+	  cert URL to ensure that it starts with http.
+
+	  https://wiki.asterisk.org/wiki/display/AST/OpenSIPit+2021
+
+	  Change-Id: Idee5b1b5e45bc3b483b3070e46ce322dca5b3f1c
+
+2021-05-24 13:38 +0000 [44fde9f428]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_pjsip: On partial transport reload also move factories.
+
+	  For connection oriented transports PJSIP uses factories to
+	  produce transports. When doing a partial transport reload
+	  we need to also move the factory of the transport over so
+	  that anything referencing the transport (such as an endpoint)
+	  has the factory available.
+
+	  ASTERISK-29441
+
+	  Change-Id: Ieae0fb98eab2d9257cad996a1136e5a62d307161
+
+2021-05-20 08:18 +0000 [19b5097d87]  Naveen Albert <mail@interlinked.x10host.com>
+
+	* func_volume: Add read capability to function.
+
+	  Up until now, the VOLUME function has been write
+	  only, so that TX/RX values can be set but not
+	  read afterwards. Now, previously set TX/RX values
+	  can be read later.
+
+	  ASTERISK-29439
+
+	  Change-Id: Ia23e92fa2e755c36e9c8e69f2940d2703ccccb5f
+
+2021-04-13 02:57 +0000 [2193cf1b26]  Evgenios_Greek <jone1984@hotmail.com>
+
+	* stasis: Fix "FRACK!, Failed assertion bad magic number" when unsubscribing
+
+	  When unsubscribing from an endpoint technology a FRACK
+	  would occur due to incorrect reference counting. This fixes
+	  that issue, along with some other issues.
+
+	  Fixed a typo in get_subscription when calling ao2_find as it
+	  needed to pass the endpoint ID and not the entire object.
+
+	  Fixed scenario where a subscription would get returned when
+	  it shouldn't have been when searching based on endpoint
+	  technology.
+
+	  A doulbe unreference has also been resolved by only explicitly
+	  releasing the reference held by tech_subscriptions.
+
+	  ASTERISK-28237 #close
+	  Reported by: Lucas Tardioli Silveira
+
+	  Change-Id: Ia91b15f8e5ea68f850c66889a6325d9575901729
+
+2021-05-20 02:15 +0000 [98e4119642]  Joseph Nadiv <ynadiv@corpit.xyz>
+
+	* res_pjsip.c: Support endpoints with domain info in username
+
+	  In multidomain environments, it is desirable to create
+	  PJSIP endpoints with the domain info in the endpoint name
+	  in pjsip_endpoint.conf.  This resulted in an error with
+	  registrations, NOTIFY, and OPTIONS packet generation.
+
+	  This commit will detect if there is an @ in the endpoint
+	  identifier and generate the URI accordingly so NOTIFY and
+	  OPTIONS From headers will generate correctly.
+
+	  ASTERISK-28393
+
+	  Change-Id: I96f8d01dfdd5573ba7a28299e46271dd4210b619
+
+2021-05-20 07:51 +0000 [a985e5069c]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_rtp_asterisk: Set correct raddr port on RTCP srflx candidates.
+
+	  RTCP ICE candidates use a base address derived from the RTP
+	  candidate. The port on the base address was not being updated to
+	  the RTCP port.
+
+	  This change sets the base port to the RTCP port and all is well.
+
+	  ASTERISK-29433
+
+	  Change-Id: Ide2d2115b307bfd3c2dfbc4d187515d724519040
+
+2021-05-25 05:38 +0000 [987f5eb0ad]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* asterisk: We've moved to Libera Chat!
+
+	  Change-Id: I48c1933dd79b50ddc0a6793acec4754b4e95c575
+
+2021-05-19 13:13 +0000 [d162789c4d]  Jeremy Lainé <jeremy.laine@m4x.org>
+
+	* res_rtp_asterisk: make it possible to remove SOFTWARE attribute
+
+	  By default Asterisk reports the PJSIP version in a SOFTWARE attribute
+	  of every STUN packet it sends. This may not be desired in a production
+	  environment, and RFC5389 recommends making the use of the SOFTWARE
+	  attribute a configurable option:
+
+	  https://datatracker.ietf.org/doc/html/rfc5389#section-16.1.2
+
+	  This patch adds a `stun_software_attribute` yes/no option to make it
+	  possible to omit the SOFTWARE attribute from STUN packets.
+
+	  ASTERISK-29434
+
+	  Change-Id: Id3f2b1dd9584536ebb3a1d7e8395fd8b3e46860b
+
+2021-04-15 10:43 +0000 [9cc1d6fc22]  George Joseph <gjoseph@digium.com>
+
+	* res_pjsip_outbound_authenticator_digest: Be tolerant of RFC8760 UASs
+
+	  RFC7616 and RFC8760 allow more than one WWW-Authenticate or
+	  Proxy-Authenticate header per realm, each with different digest
+	  algorithms (including new ones like SHA-256 and SHA-512-256).
+	  Thankfully however a UAS can NOT send back multiple Authenticate
+	  headers for the same realm with the same digest algorithm.  The
+	  UAS is also supposed to send the headers in order of preference
+	  with the first one being the most preferred.  We're supposed to
+	  send an Authorization header for the first one we encounter for a
+	  realm that we can support.
+
+	  The UAS can also send multiple realms, especially when it's a
+	  proxy that has forked the request in which case the proxy will
+	  aggregate all of the Authenticate headers and then send them all
+	  back to the UAC.
+
+	  It doesn't stop there though... Each realm can require a
+	  different username from the others.  There's also nothing
+	  preventing each digest algorithm from having a unique password
+	  although I'm not sure if that adds any benefit.
+
+	  So now... For each Authenticate header we encounter, we have to
+	  determine if we support the digest algorithm and, if not, just
+	  skip the header.  We then have to find an auth object that
+	  matches the realm AND the digest algorithm or find a wildcard
+	  object that matches the digest algorithm. If we find one, we add
+	  it to the results vector and read the next Authenticate header.
+	  If the next header is for the same realm AND we already added an
+	  auth object for that realm, we skip the header. Otherwise we
+	  repeat the process for the next header.
+
+	  In the end, we'll have accumulated a list of credentials we can
+	  pass to pjproject that it can use to add Authentication headers
+	  to a request.
+
+	  NOTE: Neither we nor pjproject can currently handle digest
+	  algorithms other than MD5.  We don't even have a place for it in
+	  the ast_sip_auth object. For this reason, we just skip processing
+	  any Authenticate header that's not MD5.  When we support the
+	  others, we'll move the check into the loop that searches the
+	  objects.
+
+	  Changes:
+
+	   * Added a new API ast_sip_retrieve_auths_vector() that takes in
+	     a vector of auth ids (usually supplied on a call to
+	     ast_sip_create_request_with_auth()) and populates another
+	     vector with the actual objects.
+
+	   * Refactored res_pjsip_outbound_authenticator_digest to handle
+	     multiple Authenticate headers and set the stage for handling
+	     additional digest algorithms.
+
+	   * Added a pjproject patch that allows them to ignore digest
+	     algorithms they don't support.  This patch has already been
+	     merged upstream.
+
+	   * Updated documentation for auth objects in the XML and
+	     in pjsip.conf.sample.
+
+	   * Although res_pjsip_authenticator_digest isn't affected
+	     by this change, some debugging and a testsuite AMI event
+	     was added to facilitate testing.
+
+	  Discovered during OpenSIPit 2021.
+
+	  ASTERISK-29397
+
+	  Change-Id: I3aef5ce4fe1d27e48d61268520f284d15d650281
+
+2021-04-14 09:44 +0000 [3cccdf6d98]  Joseph Nadiv <ynadiv@corpit.xyz>
+
+	* res_pjsip_dialog_info_body_generator: Add LOCAL/REMOTE tags in dialog-info+xml
+
+	  RFC 4235 Section 4.1.6 describes XML elements that should be
+	  sent to subscribed endpoints to identify the local and remote
+	  participants in the dialog.
+
+	  This patch adds this functionality to PJSIP by iterating through the
+	  ringing channels causing the NOTIFY, and inserts the channel info
+	  into the dialog so that information is properly passed to the endpoint
+	  in dialog-info+xml.
+
+	  ASTERISK-24601
+	  Patch submitted: Joshua Elson
+	  Modified by: Joseph Nadiv and Sean Bright
+	  Tested by: Joseph Nadiv
+
+	  Change-Id: I20c5cf5b45f34d7179df6573c5abf863eb72964b
+
+2021-05-13 10:32 +0000 [04454fc238]  Naveen Albert <mail@interlinked.x10host.com>
+
+	* AMI: Add AMI event to expose hook flash events
+
+	  Although Asterisk can receive and propogate flash events, it currently
+	  provides no mechanism for doing anything with them itself.
+
+	  This AMI event allows flash events to be processed by Asterisk.
+	  Additionally, AST_CONTROL_FLASH is included in a switch statement
+	  in channel.c to avoid throwing a warning when we shouldn't.
+
+	  ASTERISK-29380
+
+	  Change-Id: Ie17ffe65086e0282c88542e38eed6a461ec79e81
+
+2021-05-13 09:47 +0000 [567ea5abf8]  Naveen Albert <mail@interlinked.x10host.com>
+
+	* app_voicemail: Configurable voicemail beep
+
+	  Hitherto, VoiceMail() played a non-customizable beep tone to indicate
+	  the caller could leave a message. In some cases, the beep may not
+	  be desired, or a different tone may be desired.
+
+	  To increase flexibility, a new option allows customization of the tone.
+	  If the t option is specified, the default beep will be overridden.
+	  Supplying an argument will cause it to use the specified file for the tone,
+	  and omitting it will cause it to skip the beep altogether. If the option
+	  is not used, the default behavior persists.
+
+	  ASTERISK-29349
+
+	  Change-Id: I1c439c0011497e28a28067fc1cf1e654c8843280
+
+2021-05-13 10:13 +0000 [0026aeada3]  Naveen Albert <mail@interlinked.x10host.com>
+
+	* main/file.c: Don't throw error on flash event.
+
+	  AST_CONTROL_FLASH isn't accounted for in a switch statement in file.c
+	  where it should be ignored. Adding this to the switch ensures a
+	  warning isn't thrown on RFC2833 flash events, since nothing's amiss.
+
+	  ASTERISK-29372
+
+	  Change-Id: I4fa549bfb7ba1894a4044de999ea124877422fbc
+
+2021-05-13 08:50 +0000 [fd40752954]  Naveen Albert <mail@interlinked.x10host.com>
+
+	* chan_sip: Expand hook flash recognition.
+
+	  Some ATAs send hook flash events as application/hook-flash, rather than a DTMF
+	  event. Now, we also recognize hook-flash as a flash event.
+
+	  ASTERISK-29370
+
+	  Change-Id: I1c3b82a040dff3affcd94bad8ce33edc90c04725
+
+2021-05-11 12:00 +0000 [49c2e7e307]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* pjsip: Add patch for resolving STUN packet lifetime issues.
+
+	  In some cases it was possible for a STUN packet to be destroyed
+	  prematurely or even destroyed partially multiple times.
+
+	  This patch provided by Teluu fixes the lifetime of these
+	  packets and ensures they aren't partially destroyed multiple
+	  times.
+
+	  https://github.com/pjsip/pjproject/pull/2709
+
+	  ASTERISK-29377
+
+	  Change-Id: Ie842ad24ddf345e01c69a4d333023f05f787abca
+
+2021-05-12 21:20 +0000 [1b41629447]  Sean Bright <sean.bright@gmail.com>
+
+	* chan_pjsip: Correct misleading trace message
+
+	  ASTERISK-29358 #close
+
+	  Change-Id: I050daff67066873df4e8fc7f4bd977c1ca06e647
+
+2021-04-26 17:00 +0000 [0564d12280]  Ben Ford <bford@digium.com>
+
+	* STIR/SHAKEN: Switch to base64 URL encoding.
+
+	  STIR/SHAKEN encodes using base64 URL format. Currently, we just use
+	  base64. New functions have been added that convert to and from base64
+	  encoding.
+
+	  The origid field should also be an UUID. This means there's no reason to
+	  have it as an option in stir_shaken.conf, as we can simply generate one
+	  when creating the Identity header.
+
+	  https://wiki.asterisk.org/wiki/display/AST/OpenSIPit+2021
+
+	  Change-Id: Icf094a2a54e87db91d6b12244c9f5ba4fc2e0b8c
+
+2021-05-11 12:26 +0000 [05f7bc9c66]  Ben Ford <bford@digium.com>
+
+	* STIR/SHAKEN: OPENSSL_free serial hex from openssl.
+
+	  We're getting the serial number of the certificate from openssl and
+	  freeing it with ast_free(), but it needs to be freed with OPENSSL_free()
+	  instead. Now we duplicate the string and free the one from openssl with
+	  OPENSSL_free(), which means we can still use ast_free() on the returned
+	  string.
+
+	  https://wiki.asterisk.org/wiki/display/AST/OpenSIPit+2021
+
+	  Change-Id: Ia6e1a4028c1933a0e1d204b769ebb9f5a11f00ab
+
+2021-04-21 11:12 +0000 [259ecfa289]  Ben Ford <bford@digium.com>
+
+	* STIR/SHAKEN: Fix certificate type and storage.
+
+	  During OpenSIPit, we found out that the public certificates must be of
+	  type X.509. When reading in public keys, we use the corresponding X.509
+	  functions now.
+
+	  We also discovered that we needed a better naming scheme for the
+	  certificates since certificates with the same name would cause issues
+	  (overwriting certs, etc.). Now when we download a public certificate, we
+	  get the serial number from it and use that as the name of the cached
+	  certificate.
+
+	  The configuration option public_key_url in stir_shaken.conf has also
+	  been renamed to public_cert_url, which better describes what the option
+	  is for.
+
+	  https://wiki.asterisk.org/wiki/display/AST/OpenSIPit+2021
+
+	  Change-Id: Ia00b20835f5f976e3603797f2f2fb19672d8114d
+
+2021-04-22 13:07 +0000 [09303e8e22]  George Joseph <gjoseph@digium.com>
+
+	* Updates for the MessageSend Dialplan App
+
+	  Enhancements:
+
+	   * The MessageSend dialplan application now takes an optional
+	     third argument that can set the message's "To" field on
+	     outgoing messages.  It's an alternative to using the
+	     MESSAGE(to) dialplan function.
+
+	     NOTE: No channel driver currently implements this field.  A
+	     follow-on commit for res_pjsip_messaging will implement it for
+	     the chan_pjsip channel driver.
+
+	   * To prevent confusion with the first argument, currently named
+	     "to", it's been renamed to "destination". Its function,
+	     creating the request URI, hasn't changed.
+
+	   * The documentation for MessageSend was updated to be
+	     more clear about the parameters and how they interact
+	     the MESSAGE() dialplan function.
+
+	   * With the rename of MessageSend's first parameter, and the fact
+	     that message.c references <info> elements in chan_sip.c,
+	     res_pjsip_messaging.c and res_xmpp, they each needed
+	     documentation updates to use MessageDestinationInfo instead of
+	     MessageToInfo.
+
+	   * appdocsxml.dtd was updated to include a missing element
+	     declaration for "dataType".  This was showing up as an error
+	     in Eclipse's dtd editor.
+
+	   * Despite the changes in this commit, there should be
+	     no impact to current users of MessageSend.
+
+	  Change-Id: I6fb5b569657a02866a66ea352fd53d30d8ac965a
+
+2021-04-30 15:21 +0000 [e39efabd97]  Sean Bright <sean.bright@gmail.com>
+
+	* translate.c: Avoid refleak when checking for a translation path
+
+	  Change-Id: Idbd61ff77545f4a78b06a5064b55112e774b70e6
+
+2021-04-27 12:31 +0000 [b1807d440e]  Sean Bright <sean.bright@gmail.com>
+
+	* res_rtp_asterisk: More robust timestamp checking
+
+	  We assume that a timestamp value of 0 represents an 'uninitialized'
+	  timestamp, but 0 is a valid value. Add a simple wrapper to be able to
+	  differentiate between whether the value is set or not.
+
+	  This also removes the fix for ASTERISK~28812 which should not be
+	  needed if we are checking the last timestamp appropriately.
+
+	  ASTERISK-29030 #close
+
+	  Change-Id: Ie70d657d580d9a1f2877e25a6ef161c5ad761cf7
+
+2021-04-28 07:17 +0000 [f142ca254e]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* chan_local: Skip filtering audio formats on removed streams.
+
+	  When a stream topology is provided to chan_local when dialing
+	  it filters the audio formats down. This operation did not skip
+	  streams which were removed (that have no formats) resulting in
+	  calling being aborted.
+
+	  This change causes such streams to be skipped.
+
+	  ASTERISK-29407
+
+	  Change-Id: I1de8b98727cb2d10f4bc287da0b5fdcb381addd6
+
+2021-04-23 12:37 +0000 [4a843e00ef]  Sean Bright <sean.bright@gmail.com>
+
+	* res_pjsip.c: OPTIONS processing can now optionally skip authentication
+
+	  ASTERISK-27477 #close
+
+	  Change-Id: I68f6715bba92a525149e35d142a49377a34a1193
+
+2021-04-21 06:42 +0000 [55279bfd9c]  Jean Aunis <jean.aunis@prescom.fr>
+
+	* translate.c: Take sampling rate into account when checking codec's buffer size
+
+	  Up/down sampling changes the number of samples produced by a translation.
+	  This must be taken into account when checking the codec's buffer size.
+
+	  ASTERISK-29328
+
+	  Change-Id: I9aebe2f8788e00321a7f5c47aa97c617f39e9055
+
+2021-04-25 04:45 +0000 [531eb65cf3]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* svn: Switch to https scheme.
+
+	  Some versions of SVN seemingly don't follow the redirect
+	  to https.
+
+	  Change-Id: Ia7c76c18cb620bcf56f08e1211a7d80d321fe253
+
+2021-04-20 08:42 +0000 [512d38868c]  George Joseph <gjoseph@digium.com>
+
+	* res_pjsip:  Update documentation for the auth object
+
+	  Change-Id: I2f76867ce02ec611964925159be099de83346e38
+
+2021-03-29 12:28 +0000 [45a1977de4]  Ben Ford <bford@digium.com>
+
+	* res_aeap: Add basic config skeleton and CLI commands.
+
+	  Added support for a basic AEAP configuration read from aeap.conf.
+	  Also added 2 CLI commands for showing individual configurations as
+	  well as all of them: aeap show server <id> and aeap show servers.
+
+	  Only one configuration option is required at the moment, and that one is
+	  server_url. It must be a websocket URL. The other option, codecs, is
+	  optional and will be used over the codecs specified on the endpoint if
+	  provided.
+
+	  https://wiki.asterisk.org/wiki/pages/viewpage.action?pageId=45482453
+
+	  Change-Id: I567ac5148c92b98d29d2ad83421b416b75ffdaa3
+
+2021-04-02 07:21 +0000 [44aef0449a]  George Joseph <gjoseph@digium.com>
+
+	* bridge_channel_write_frame: Check for NULL channel
+
+	  There is a possibility, when bridge_channel_write_frame() is
+	  called, that the bridge_channel->chan will be NULL.  The first
+	  thing bridge_channel_write_frame() does though is call
+	  ast_channel_is_multistream() which had no check for a NULL
+	  channel and therefore caused a segfault. Since it's still
+	  possible for bridge_channel_write_frame() to write the frame to
+	  the other channels in the bridge, we don't want to bail before we
+	  call ast_channel_is_multistream() but we can just skip the
+	  multi-channel stuff.  So...
+
+	  bridge_channel_write_frame() only calls ast_channel_is_multistream()
+	  if bridge_channel->chan is not NULL.
+
+	  As a safety measure, ast_channel_is_multistream() now returns
+	  false if the supplied channel is NULL.
+
+	  ASTERISK-29379
+	  Reported-by: Vyrva Igor
+	  Reported-by: Ross Beer
+
+	  Change-Id: Idfe62dbea8c69813ecfd58e113a6620dc42352ce
+
+2021-04-01 10:38 +0000 [5a13e95c56]  Sean Bright <sean.bright@gmail.com>
+
+	* loader.c: Speed up deprecation metadata lookup
+
+	  Only use an XPath query once per module, then just navigate the DOM for
+	  everything else.
+
+	  Change-Id: Ia0336a7185f9180ccba4b6f631a00f9a22a36e92
+
+2021-04-01 08:39 +0000 [53c702e1cc]  George Joseph <gjoseph@digium.com>
+
+	* res_prometheus: Clone containers before iterating
+
+	  The channels, bridges and endpoints scrape functions were
+	  grabbing their respective global containers, getting the
+	  count of entries, allocating metric arrays based on
+	  that count, then iterating over the container.  If the
+	  global container had new objects added after the count
+	  was taken and the metric arrays were allocated, we'd run
+	  out of metric entries and attempt to write past the end
+	  of the arrays.
+
+	  Now each of the scape functions clone their respective
+	  global containers and all operations are done on the
+	  clone.  Since the clone is stable between getting the
+	  count and iterating over it, we can't run past the end
+	  of the metrics array.
+
+	  ASTERISK-29130
+	  Reported-By: Francisco Correia
+	  Reported-By: BJ Weschke
+	  Reported-By: Sébastien Duthil
+
+	  Change-Id: If0c8e40853bc0e9429f2ba9c7f5f358d90c311af
+
+2021-03-10 09:03 +0000 [46ed6af9c2]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* loader: Output warnings for deprecated modules.
+
+	  Using the information from the MODULEINFO XML we can
+	  now output useful information at the end of module
+	  loading for deprecated modules. This includes the
+	  version it was deprecated in, the version it will be
+	  removed in, and the replacement if available.
+
+	  ASTERISK-29339
+
+	  Change-Id: I2080dab97d2186be94c421b41dabf6d79a11611a
+
+2021-03-22 15:22 +0000 [0fc906a5e1]  Kevin Harwell <kharwell@sangoma.com>
+
+	* res_rtp_asterisk: Fix standard deviation calculation
+
+	  For some input to the standard deviation algorithm extremely large,
+	  and wrong numbers were being calculated.
+
+	  This patch uses a new formula for correctly calculating both the
+	  running mean and standard deviation for the given inputs.
+
+	  ASTERISK-29364 #close
+
+	  Change-Id: Ibc6e18be41c28bed3fde06d612607acc3fbd621f
+
+2021-03-29 17:40 +0000 [c4a376aac2]  Kevin Harwell <kharwell@sangoma.com>
+
+	* res_rtp_asterisk: Don't count 0 as a minimum lost packets
+
+	  The calculated minimum lost packets represents the lowest number of
+	  lost packets missed during an RTCP report interval. Zero of course
+	  is the lowest, but the idea is that this value contain the lowest
+	  number of lost packets once some have been missed.
+
+	  This patch checks to make sure the number of lost packets over an
+	  interval is not zero before checking and setting the minimum value.
+
+	  Also, this patch updates the rtp lost packet test to check for
+	  packet loss over several reports vs one.
+
+	  Change-Id: I07d6e21cec61e289c2326138d6bcbcb3c3d5e008
+
+2021-03-31 12:17 +0000 [65b68fd060]  Kevin Harwell <kharwell@sangoma.com>
+
+	* res_rtp_asterisk: Statically declare rtp_drop_packets_data object
+
+	  This patch makes the drop_packets_data object static.
+
+	  Change-Id: If4f9b21fa0c47d41a35b6b05941d978efb4da87b
+
+2021-03-29 17:52 +0000 [8bd13a995a]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_rtp_asterisk: Only raise flash control frame on end.
+
+	  Flash in RTP is conveyed the same as DTMF, just with a
+	  specific digit. In Asterisk however we do flash as a
+	  single control frame.
+
+	  This change makes it so that only on end do we provide
+	  the flash control frame to the core. Previously we would
+	  provide a flash control frame on both begin and end,
+	  causing flash to work improperly.
+
+	  ASTERISK-29373
+
+	  Change-Id: I1accd9c6e859811336e670e698bd8bd124f33226
+
+2021-03-05 12:53 +0000 [b86f1ef54c]  Kevin Harwell <kharwell@sangoma.com>
+
+	* res_rtp_asterisk: Add a DEVMODE RTP drop packets CLI command
+
+	  This patch makes it so when Asterisk is compiled in DEVMODE a CLI
+	  command is available that allows someone to drop incoming RTP
+	  packets. The command allows for dropping of packets once, or on a
+	  timed interval (e.g. drop 10 packets every 5 seconds). A user can
+	  also specify to drop packets by IP address.
+
+	  Change-Id: I25fa7ae9bad6ed68e273bbcccf0ee51cae6e7024
+
+2021-03-30 06:59 +0000 [623abc2b6a]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_pjsip: Give error when TLS transport configured but not supported.
+
+	  Change-Id: I058af496021ff870ccec2d8cbade637b348ab80b
+
+2021-03-05 12:47 +0000 [eb92fb7298]  Kevin Harwell <kharwell@sangoma.com>
+
+	* time: Add timeval create and unit conversion functions
+
+	  Added a TIME_UNIT enumeration, and a function that converts a
+	  string to one of the enumerated values. Also, added functions
+	  that create and initialize a timeval object using a specified
+	  value, and unit type.
+
+	  Change-Id: Ic31a1c3262a44f77a5ef78bfc85dcf69a8d47392
+
+2021-03-24 08:38 +0000 [8db2a34065]  Sean Bright <sean.bright@gmail.com>
+
+	* app_queue: Add alembic migration to add ringinuse to queue_members.
+
+	  ASTERISK-28356 #close
+
+	  Change-Id: I53a1bfdd3113d620bea88349019173a2f3f0ae39
+
+2021-03-28 10:47 +0000 [c2dbfb9a8e]  Sean Bright <sean.bright@gmail.com>
+
+	* modules.conf: Fix more differing usages of assignment operators.
+
+	  I missed the changes in 18 and master in the previous review.
+
+	  ASTERISK-24434 #close
+
+	  Change-Id: Ieb132b2a998ce96daa9c9acf26535a974b895876
+
+2021-03-24 10:52 +0000 [25758670b8]  Ben Ford <bford@digium.com>
+
+	* logger.conf.sample: Add more debug documentation.
+
+	  Change-Id: Iff0e713f2120d8dce8e1e26924b99ed17f9d9dff
+
+2021-03-24 10:59 +0000 [55c53de022]  Ben Ford <bford@digium.com>
+
+	* logging: Add .log to samples and update asterisk.logrotate.
+
+	  Added .log extension to the sample logs in logger.conf.sample so that
+	  they will be able to be opened in the browser when attached to JIRA
+	  tickets. Because of this, asterisk.logrotate has also been updated to
+	  look for .log extensions instead of no extension for log files such as
+	  full and messages.
+
+	  Change-Id: I5de743c03f08047d6c6cc80cac5019ae0c4c200f
+
+2021-03-23 15:15 +0000 [aac442eecd]  Sean Bright <sean.bright@gmail.com>
+
+	* app_queue.c: Remove dead 'updatecdr' code.
+
+	  Also removed the sample documentation, and some oddly-placed
+	  documentation about the timeout argument to the Queue() application
+	  itself. There is a large section on the timeout behavior below.
+
+	  ASTERISK-26614 #close
+
+	  Change-Id: I8f84e8304b50305b7c4cba2d9787a5d77c3a6217
+
+2021-03-23 17:24 +0000 [cad843fe07]  Sean Bright <sean.bright@gmail.com>
+
+	* queues.conf.sample: Correct 'context' documentation.
+
+	  ASTERISK-24631 #close
+
+	  Change-Id: I8bf8776906a72ee02f24de6a85345940b9ff6b6f
+
+2021-03-19 09:11 +0000 [b4347c4861]  Mark Murawski <markm@intellasoft.net>
+
+	* logger: Console sessions will now respect logger.conf dateformat= option
+
+	  The 'core' console (ie: asterisk -c) does read logger.conf and does
+	  use the dateformat= option.
+
+	  Whereas 'remote' consoles (ie: asterisk -r -T) does not read logger.conf
+	  and uses a hard coded dateformat option for printing received verbose messages:
+	    main/logger.c: static char dateformat[256] = "%b %e %T"
+
+	  This change will load logger.conf for each remote console session and
+	  use the dateformat= option to set the per-line timestamp for verbose messages
+
+	  Change-Id: I3ea10990dbd920e9f7ce8ff771bc65aa7f4ea8c1
+	  ASTERISK-25358: #close
+	  Reported-by: Igor Liferenko
+
+2021-03-19 15:57 +0000 [8d3d7bdb82]  Sean Bright <sean.bright@gmail.com>
+
+	* app_queue.c: Don't crash when realtime queue name is empty.
+
+	  ASTERISK-27542 #close
+
+	  Change-Id: If0b9719380a25533d2aed1053cff845dc3a4854a
+
+2021-03-18 11:14 +0000 [a03a05195a]  George Joseph <gjoseph@digium.com>
+
+	* res_pjsip_session: Make reschedule_reinvite check for NULL topologies
+
+	  When the check for equal topologies was added to reschedule_reinvite()
+	  it was assumed that both the pending and active media states would
+	  actually have non-NULL topologies.  We since discovered this isn't
+	  the case.
+
+	  We now only test for equal topologies if both media states have
+	  non-NULL topologies.  The logic had to be rearranged a bit to make
+	  sure that we cloned the media states if their topologies were
+	  non-NULL but weren't equal.
+
+	  ASTERISK-29215
+
+	  Change-Id: I61313cca7fc571144338aac826091791b87b6e17
+
+2021-03-19 04:56 +0000 [a8a08bcd1e]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* app_queue: Only send QueueMemberStatus if status changes.
+
+	  If a queue member was updated with the same status multiple
+	  times each time a QueueMemberStatus event would be sent
+	  which would be a duplicate of the previous.
+
+	  This change makes it so that the QueueMemberStatus event is
+	  only sent if the status actually changes.
+
+	  ASTERISK-29355
+
+	  Change-Id: I580c60d992a0a8f2bea8b91c868771b3b490d116
+
+2021-03-19 08:52 +0000 [970b84946e]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* core_unreal: Fix deadlock with T.38 control frames.
+
+	  When using the ast_unreal_lock_all function no channel
+	  locks can be held before calling it.
+
+	  This change unlocks the channel that indicate was
+	  called on before doing so and then relocks it afterwards.
+
+	  ASTERISK-29035
+
+	  Change-Id: Id65016201b5f9c9519a216e250f9101c629e19e9
+
+2021-03-01 17:32 +0000 [71dfbdc7b9]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_pjsip: Add support for partial transport reload.
+
+	  Some configuration items for a transport do not result in
+	  the underlying transport changing, but instead are just
+	  state we keep ourselves and use. It is perfectly reasonable
+	  to change these items.
+
+	  These include local_net and external_* information.
+
+	  ASTERISK-29354
+
+	  Change-Id: I027857ccfe4419f460243e562b5f098434b3d43a
+
+2021-03-13 05:01 +0000 [fc03116d9b]  Jaco Kroon <jaco@uls.co.za>
+
+	* menuselect: exit non-zero in case of failure on --enable|disable options.
+
+	  ASTERISK-29348
+
+	  Change-Id: I77e3466435f5a51a57538b29addb68d811af238d
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+2021-03-17 10:28 +0000 [cce5ee5b7a]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_rtp_asterisk: Force resync on SSRC change.
+
+	  When an SSRC change occurs the timestamps are likely
+	  to change as well. As a result we need to reset the
+	  timestamp mapping done in the calc_rxstamp function
+	  so that they map properly from timestamp to real
+	  time.
+
+	  This previously occurred but due to packet
+	  retransmission support the explicit setting
+	  of the marker bit was not effective.
+
+	  ASTERISK-29352
+
+	  Change-Id: I2d4c8f93ea24abc1030196706de2d70facf05a5a
+
+2021-03-10 08:05 +0000 [efc61a96f0]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* menuselect: Add ability to set deprecated and removed versions.
+
+	  The "deprecated_in" and "removed_in" information can now be
+	  set in MODULEINFO for a module and is then displayed in
+	  menuselect so users can be aware of when a module is slated
+	  to be deprecated and then removed.
+
+	  ASTERISK-29337
+
+	  Change-Id: I6952889cf08e0e9e99cf8b43f99b3cef4688087a
+
+2021-03-10 08:18 +0000 [3330fb41f4]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* xml: Allow deprecated_in and removed_in for MODULEINFO.
+
+	  ASTERISK-29337
+
+	  Change-Id: I2211b7da8d29369f8649aeabce07679da0787f2b
+
+2021-03-09 08:54 +0000 [149e5e5b86]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* xml: Embed module information into core XML documentation.
+
+	  This change embeds the MODULEINFO block of modules
+	  into the core XML documentation. This provides a shared
+	  mechanism for use by both menuselect and Asterisk for
+	  information and a definitive source of truth.
+
+	  ASTERISK-29335
+
+	  Change-Id: Ifbfd5c700049cf320a3e45351ac65dd89bc99d90
+
+2021-03-10 04:47 +0000 [7438586d8e]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* documentation: Fix non-matching module support levels.
+
+	  Some modules have a different support level documented in their
+	  MODULEINFO XML and Asterisk module definition. This change
+	  brings the two in sync for the modules which were not matching.
+
+	  ASTERISK-29336
+
+	  Change-Id: If2f819103d4a271e2e0624ef4db365e897fa3d35
+
+2021-03-09 18:35 +0000 [cc127a999c]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* channel: Fix crash in suppress API.
+
+	  There exists an inconsistency with framehook usage
+	  such that it is only on reads that the frame should
+	  be freed, not on writes as well.
+
+	  ASTERISK-29071
+
+	  Change-Id: I5ef918ebe4debac8a469e8d43bf9d6b673e8e472
+
+2021-02-24 12:00 +0000 [41389bfdbd]  Jaco Kroon <jaco@uls.co.za>
+
+	* func_callerid+res_agi: Fix compile errors related to -Werror=zero-length-bounds
+
+	  Change-Id: I75152cece8a00b7523d542e5ac22796f9595692b
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+2021-02-24 12:34 +0000 [8acb4fbd1e]  Jaco Kroon <jaco@uls.co.za>
+
+	* app.h: Fix -Werror=zero-length-bounds compile errors in dev mode.
+
+	  Change-Id: I5c104dc1f8417ccd3d01faf86e84ccbf89bc3b31
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+2021-03-06 16:57 +0000 [8987de270f]  Sean Bright <sean.bright@gmail.com>
+
+	* app_dial.c: Only send DTMF on first progress event.
+
+	  ASTERISK-29329 #close
+
+	  Change-Id: Ic58e7a17f1ff3f785a5b21dced88682581149601
+
+2021-03-05 11:16 +0000 [1ae40e502d]  Alexander Traud <pabstraud@compuserve.com>
+
+	* res_format_attr_*: Parameter Names are Case-Insensitive.
+
+	  see RFC 4855:
+	  parameter names are case-insensitive both in media type strings and
+	  in the default mapping to the SDP a=fmtp attribute.
+
+	  This change is required for H.263+ because some implementations are
+	  known to use even mixed-case. This does not fix ASTERISK~29268 because
+	  H.264 was not fixed. This approach here lowers/uppers both parameter
+	  names and parameter values. H.264 needs a different approach because
+	  one of its parameter values is not case-insensitive:
+	  sprop-parameter-sets is Base64.
+
+	  Change-Id: Idf2a73457be231647aed3c87b1da197afba86892
+
+2021-03-05 11:45 +0000 [8c461845c8]  Alexander Traud <pabstraud@compuserve.com>
+
+	* chan_iax2: System Header strings is included via asterisk.h/compat.h.
+
+	  The system header strings was included mistakenly with commit 3de0204.
+	  That header is included via asterisk.h and there via the compat.h.
+
+	  Change-Id: I3dc49060e275295f785670c87cc65fd3c3abd24a
+
+2021-03-08 15:43 +0000 [55bd104589]  Sean Bright <sean.bright@gmail.com>
+
+	* modules.conf: Fix differing usage of assignment operators.
+
+	  ASTERISK-24434 #close
+
+	  Change-Id: I0144e8d65d878128da59dcf3df12ca8cee47d6db
+
+2021-03-08 14:06 +0000 [30e509c2f9]  Sean Bright <sean.bright@gmail.com>
+
+	* strings.h: ast_str_to_upper() and _to_lower() are not pure.
+
+	  Because they modify their argument they are not pure functions and
+	  should not be marked as such, otherwise the compiler may optimize
+	  them away.
+
+	  ASTERISK-29306 #close
+
+	  Change-Id: Ibec03a08522dd39e8a137ece9bc6a3059dfaad5f
+
+2021-03-08 17:16 +0000 [df37b8181c]  Sean Bright <sean.bright@gmail.com>
+
+	* res_musiconhold.c: Plug ref leak caused by ao2_replace() misuse.
+
+	  ao2_replace() bumps the reference count of the object that is doing the
+	  replacing, which is not what we want. We just want to drop the old ref
+	  on the old object and update the pointer to point to the new object.
+
+	  Pointed out by George Joseph in #asterisk-dev
+
+	  Change-Id: Ie8167ed3d4b52b9d1ea2d785f885e8c27206743d
+
+2021-02-19 05:50 +0000 [8c247e2a94]  Torrey Searle <tsearle@voxbone.com>
+
+	* res/res_rtp_asterisk: generate new SSRC on native bridge end
+
+	  For RTCP to work, we update the ssrc to be the one corresponding to
+	  the native bridge while active.  However when the bridge ends we
+	  should generate a new SSRC as the sequence numbers will not continue
+	  from the native bridge left off.
+
+	  ASTERISK-29300 #close
+
+	  Change-Id: I23334b6934d2bf6490bda4bbf6414d96b8d17d10
+
+2021-03-01 15:35 +0000 [304f8ddfb2]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* sorcery: Add support for more intelligent reloading.
+
+	  Some sorcery objects actually contain dynamic content
+	  that can change despite the underlying configuration
+	  itself not changing. A good example of this is the
+	  res_pjsip_endpoint_identifier_ip module which allows
+	  specifying hostnames. While the configuration may not
+	  change between reloads the DNS information of the
+	  hostnames can.
+
+	  This change adds the ability for a sorcery object to be
+	  marked as having dynamic contents which is then taken
+	  into account when reloading by the sorcery file based
+	  config module. If there is an object with dynamic content
+	  then a reload will be forced while if there are none
+	  then the existing behavior of not reloading occurs.
+
+	  ASTERISK-29321
+
+	  Change-Id: I9342dc55be46cc00204533c266a68d972760a0b1
+
+2021-03-02 12:55 +0000 [607603cf89]  George Joseph <gjoseph@digium.com>
+
+	* res_pjsip_refer: Move the progress dlg release to a serializer
+
+	  Although the dlg session count was incremented in a pjsip servant
+	  thread, there's no guarantee that the last thread to unref this
+	  progress object was one.  Before we decrement, we need to make
+	  sure that this is either a servant thread or that we push the
+	  decrement to a serializer that is one.
+
+	  Because pjsip_dlg_dec_session requires the dialog lock, we don't
+	  want to wait on the task to complete if we had to push it to a
+	  serializer.
+
+	  Change-Id: I8ff2d5d94be3ff04298394070434e22a7d3cbc41
+
+2021-03-03 12:31 +0000 [6f67f24afd]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_pjsip_registrar: Include source IP and port in log messages.
+
+	  When registering it can be useful to see the source IP address and
+	  port in cases where multiple devices are using the same endpoint
+	  or when anonymous is in use.
+
+	  ASTERISK-29325
+
+	  Change-Id: Ie178a6f55f53f8473035854c411bc3d056e0a2e0
+
+2021-03-03 12:44 +0000 [f8d1758792]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* asterisk: Update copyright.
+
+	  ASTERISK-29326
+
+	  Change-Id: Ia95dbfb66e2d11ac4d1228444283bb2e4d77396a
+
+2021-02-25 13:50 +0000 [fd560ad9fa]  Ben Ford <bford@digium.com>
+
+	* AST-2021-006 - res_pjsip_t38.c: Check for session_media on reinvite.
+
+	  When Asterisk sends a reinvite negotiating T38 faxing, it's possible a
+	  crash can occur if the response contains a m=image and zero port. The
+	  reinvite callback code now checks session_media to see if it is null or
+	  not before trying to access the udptl variable on it.
+
+	  ASTERISK-29305
+
+	  Change-Id: I1dfc51c5fa586e38579ede4bc228edee213ccaa9
+
+2021-01-28 08:39 +0000 [a34e7de61c]  Alexander Traud <pabstraud@compuserve.com>
+
+	* res_format_attr_h263: Generate valid SDP fmtp for H.263+.
+
+	  Fixed:
+	  * RFC 4629 does not allow the value "0" for MPI, K, and N.
+	  * Allow value "0" for PAR.
+	  * BPP is printed only when specified because "0" has a meaning.
+
+	  New:
+	  * Added CPCF and MaxBR.
+	  * Some implementations provide CIF without MPI: a=fmtp:xx CIF;F=1
+	    Although a violation of RFC 3555 section 3, we can support that.
+
+	  Changed:
+	  * Resorts the CIFs from large to small which partly fixes ASTERISK~29267.
+
+	  Change-Id: I95a650c715007b8dde11a77cb37d9c6c123a441e
+
+2021-02-24 07:04 +0000 [2c1b6b7b15]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_pjsip_nat: Don't rewrite Contact on REGISTER responses.
+
+	  When sending a SIP response to an incoming REGISTER request
+	  we don't want to change the Contact header as it will
+	  contain the Contacts registered to the AOR and not our own
+	  Contact URI.
+
+	  ASTERISK-29235
+
+	  Change-Id: I35a0723545281dd01fcd5cae497baab58720478c
+
+2021-03-03 07:32 +0000 [3e5b9e3952]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* channel: Fix memory leak in suppress API.
+
+	  A frame suppression API exists as part of channels
+	  which allows audio frames to or from a channel to
+	  be dropped. The MuteAudio AMI action uses this
+	  API to perform its job.
+
+	  This API uses a framehook to intercept flowing
+	  audio and drop it when appropriate. It is the
+	  responsibility of the framehook to free the
+	  frame it is given if it changes the frame. The
+	  suppression API failed to do this resulting in
+	  a leak of audio frames.
+
+	  This change adds the freeing of these frames.
+
+	  ASTERISK-29071
+
+	  Change-Id: Ie50acd454d672d36af914050c327d2e120d8ba7b
+
+2021-01-27 14:01 +0000 [5d42dd2e6a]  Salah Ahmed <sahmed@voxbone.com>
+
+	* res_rtp_asterisk:  Check remote ICE reset and reset local ice attrb
+
+	  This change will check is the remote ICE session got reset or not by
+	  checking the offered ufrag and password with session. If the remote ICE
+	  reset session then Asterisk reset its local ufrag and password to reject
+	  binding request with Old ufrag and Password.
+
+	  ASTERISK-29266
+
+	  Change-Id: I9c55e79a7af98a8fbb497d336b828ba41bc34eeb
+
+2021-01-07 08:25 +0000 [48ed4f670f]  Holger Hans Peter Freyther <holger@moiji-mobile.com>
+
+	* pjsip: Generate progress (once) when receiving a 180 with a SDP
+
+	  ASTERISK-29105
+
+	  Change-Id: If1615fe7115fe544ef974b044d3cea5c48b94a38
+
+2021-02-28 03:24 +0000 [2ea75ed3d5]  Nico Kooijman <nk@voclarion.nl>
+
+	* main: With Dutch language year after 2020 is not spoken in say.c
+
+	  Implemented the english way of saying the year in ast_say_date_with_format_nl.
+	  Currently the numbers are spoken correctly until 2020 and stopped working
+	  this year.
+
+	  ASTERISK-29297 #close
+	  Reported-by: Jacek Konieczny
+
+	  Change-Id: If5918eed5ab05df31df4dd23f08a909a60f6aba4
+
+2021-02-24 20:51 +0000 [8f6e0f9367]  Nick French <nickfrench@gmail.com>
+
+	* res_pjsip: dont return early from registration if init auth fails
+
+	  If set_outbound_initial_authentication_credentials() fails,
+	  handle_client_registration() bails early without creating or
+	  sending a register message.
+
+	  [set_outbound_initial_authentication_credentials() failures
+	  can occur during the process of retrieving an oauth access
+	  token.]
+
+	  The return from handle_client_registration is ignored, so
+	  returning an error doesn't do any good.
+
+	  This is a real problem when the registration request is a
+	  re-register, because then the registration will still be
+	  marked 'active' despite the re-register never being sent at all.
+
+	  So instead, log a warning but let the registration be created
+	  and sent (and probably fail) and follow the normal registration
+	  failed retry/abort logic.
+
+	  ASTERISK-29315 #close
+
+	  Change-Id: I2e03b1ea7fba1fa1a8279086aa4b17679e7fa7fa
+
+2021-02-23 10:14 +0000 [d2f623bae2]  Alexei Gradinari <alex2grad@gmail.com>
+
+	* res_fax: validate the remote/local Station ID for UTF-8 format
+
+	  If the remote Station ID contains invalid UTF-8 characters
+	  the asterisk fails to publish the Stasis and ReceiveFax status messages.
+
+	  json.c: Error building JSON from '{s: s, s: s}': Invalid UTF-8 string.
+	  0: /usr/sbin/asterisk(ast_json_vpack+0x98) [0x4f3f28]
+	  1: /usr/sbin/asterisk(ast_json_pack+0x8c) [0x4f3fcc]
+	  2: /usr/sbin/asterisk(ast_channel_publish_varset+0x2b) [0x57aa0b]
+	  3: /usr/sbin/asterisk(pbx_builtin_setvar_helper+0x121) [0x530641]
+	  4: /usr/lib64/asterisk/modules/res_fax.so(+0x44fe) [0x7f27f4bff4fe]
+	  ...
+	  stasis_channels.c: Error creating message
+
+	  json.c: Error building JSON from '{s: s, s: s, s: s, s: s, s: s, s: s, s: o}': Invalid UTF-8 string.
+	  0: /usr/sbin/asterisk(ast_json_vpack+0x98) [0x4f3f28]
+	  1: /usr/sbin/asterisk(ast_json_pack+0x8c) [0x4f3fcc]
+	  2: /usr/lib64/asterisk/modules/res_fax.so(+0x5acd) [0x7f27f4c00acd]
+	  ...
+	  res_fax.c: Error publishing ReceiveFax status message
+
+	  This patch replaces the invalid UTF-8 Station IDs with an empty string.
+
+	  ASTERISK-29312 #close
+
+	  Change-Id: Ieb00b6ecf67db3bfca787649caa8517f29d987db
+
+2021-02-25 13:55 +0000 [932eae69ab]  Sean Bright <sean.bright@gmail.com>
+
+	* app_page.c: Don't fail to Page if beep sound file is missing
+
+	  ASTERISK-16799 #close
+
+	  Change-Id: I40367b0d6dbf66a39721bde060c8b2d734a61cf4
+
+2021-02-19 13:25 +0000 [4c9c5c985b]  George Joseph <gjoseph@digium.com>
+
+	* res_pjsip_refer: Refactor progress locking and serialization
+
+	  Although refer_progress_notify() always runs in the progress
+	  serializer, the pjproject evsub module itself can cause the
+	  subscription to be destroyed which then triggers
+	  refer_progress_on_evsub_state() to clean it up.  In this case,
+	  it's possible that refer_progress_notify() could get the
+	  subscription pulled out from under it while it's trying to use
+	  it.
+
+	  At one point we tried to have refer_progress_on_evsub_state()
+	  push the cleanup to the serializer and wait for its return before
+	  returning to pjproject but since pjproject calls its state
+	  callbacks with the dialog locked, this required us to unlock the
+	  dialog while waiting for the serialized cleanup, then lock it
+	  again before returning to pjproject. There were also still some
+	  cases where other callers of refer_progress_notify() weren't
+	  using the serializer and crashes were resulting.
+
+	  Although all callers of refer_progress_notify() now use the
+	  progress serializer, we decided to simplify the locking so we
+	  didn't have to unlock and relock the dialog in
+	  refer_progress_on_evsub_state().
+
+	  Now, refer_progress_notify() holds the dialog lock for its
+	  duration and since pjproject also holds the dialog lock while
+	  calling refer_progress_on_evsub_state() (which does the cleanup),
+	  there should be no more chances for the subscription to be
+	  cleaned up while still being used to send NOTIFYs.
+
+	  To be extra safe, we also now increment the session count on
+	  the dialog when we create a progress object and decrement
+	  the count when the progress is destroyed.
+
+	  ASTERISK-29313
+
+	  Change-Id: I97a8bb01771a3c85345649b8124507f7622a8480
+
+2021-02-24 16:05 +0000 [e5e49d7ecd]  Kevin Harwell <kharwell@sangoma.com>
+
+	* res_rtp_asterisk: Add packet subtype during RTCP debug when relevant
+
+	  For some RTCP packet types the report count is actually the packet's subtype.
+	  This was not being reflected in the packet debug output.
+
+	  This patch makes it so for some RTCP packet types a "Packet Subtype" is
+	  now output in the debug replacing the "Reception reports" (i.e count).
+
+	  Change-Id: Id4f4b77bb37077a4c4f039abd6a069287bfefcb8
+
+2021-02-16 12:33 +0000 [a81d07ea56]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_pjsip_session: Always produce offer on re-INVITE without SDP.
+
+	  When PJSIP receives a re-INVITE without an SDP offer the INVITE
+	  session library will first call the on_create_offer callback and
+	  if unavailable then use the active negotiated SDP as the offer.
+
+	  In some cases this would result in a different SDP then was
+	  previously used without an incremented SDP version number. The two
+	  known cases are:
+
+	  1. Sending an initial INVITE with a set of codecs and having the
+	  remote side answer with a subset. The active negotiated SDP would
+	  have the pruned list but would not have an incremented SDP version
+	  number.
+
+	  2. Using re-INVITE for unhold. We would modify the active negotiated
+	  SDP but would not increment the SDP version.
+
+	  To solve these, and potential other unknown cases, the on_create_offer
+	  callback has now been implemented which produces a fresh offer with
+	  incremented SDP version number. This better fits within the model
+	  provided by the INVITE session library.
+
+	  ASTERISK-28452
+
+	  Change-Id: I2d81048d54edcb80fe38fdbb954a86f0a58281a1
+
+2021-02-23 05:28 +0000 [6d2614be68]  Jaco Kroon <jaco@uls.co.za>
+
+	* res_odbc_transaction: correctly initialise forcecommit value from DSN.
+
+	  Also improve the in-process documentation to clarify that the value is
+	  initialised from the DSN and not default false, but that the DSN's value
+	  is default false if unset.
+
+	  ASTERISK-29311 #close
+
+	  Change-Id: I46e2379f7b0656034442bce77cb37ccd4e61098d
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+2021-02-15 12:24 +0000 [e1126ffc10]  Ben Ford <bford@digium.com>
+
+	* res_pjsip_session.c: Check topology on re-invite.
+
+	  Removes an unnecessary check for the conditional that compares the
+	  stream topologies to see if they are equal to suppress re-invites. This
+	  was a problem when a Digium phone received an INVITE that offered codecs
+	  different than what it supported, causing Asterisk to send the
+	  re-invite.
+
+	  ASTERISK-29303
+
+	  Change-Id: I04dc91befb2387904e28a9aaeaa3bcdbcaa7fa63
+
+2021-02-15 13:02 +0000 [b046e960af]  Boris P. Korzun <drtr0jan@yandex.ru>
+
+	* res_config_pgsql: Limit realtime_pgsql() to return one (no more) record.
+
+	  Added a SELECT 'LIMIT' clause to realtime_pgsql() and refactored the function.
+
+	  ASTERISK-29293 #close
+
+	  Change-Id: If5a6d4b1072ea2e6e89059b21139d554a74b34f5
+
+2019-09-13 08:02 +0000 [4d8fc97e4a]  Ivan Poddubnyi <ivan.poddubny@gmail.com>
+
+	* app_queue: Fix conversion of complex extension states into device states
+
+	  Queue members using dialplan hints as a state interface must handle
+	  INUSE+RINGING hint as RINGINUSE devstate, and INUSE + ONHOLD as INUSE.
+
+	  ASTERISK-28369
+
+	  Change-Id: I127e06943d4b4f1afc518f9e396de77449992b9f
+
+2021-02-10 11:59 +0000 [725eca3bfa]  Jaco Kroon <jaco@uls.co.za>
+
+	* app.h: Restore C++ compatibility for macro AST_DECLARE_APP_ARGS
+
+	  This partially reverts commit 3d1bf3c537bba0416f691f48165fdd0a32554e8a,
+	  specifically for app.h.
+
+	  This works with both gcc 9.3.0 and 10.2.0 now, both for C and C++ (as
+	  tested with external modules).
+
+	  ASTERISK-29287
+
+	  Change-Id: I5b9f02a9b290675682a1d13f1788fdda597c9fca
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+2021-02-05 06:29 +0000 [5894535fed]  Alexander Traud <pabstraud@compuserve.com>
+
+	* chan_sip: Filter pass-through audio/video formats away, again.
+
+	  Instead of looking for pass-through formats in the list of transcodable
+	  formats (which is going to find nothing), go through the result which
+	  is going to be the jointcaps of the tech_pvt of the channel. Finally,
+	  only with that list, ast_format_cap_remove(.) is going to succeed.
+
+	  This restores the behaviour of Asterisk 1.8. However, it does not fix
+	  ASTERISK_29282 because that issue report is about chan_sip and PJSIP.
+	  Here, only chan_sip is fixed because PJSIP does not even call
+	  ast_rtp_instance_available_formats -> ast_translate_available_format.
+
+	  Change-Id: Icade2366ac2b82935b95a9981678c987da2e8c34
+
+2021-02-17 14:51 +0000 [b0f349a330]  Jaco Kroon <jaco@uls.co.za>
+
+	* func_odbc:  Introduce minargs config and expose ARGC in addition to ARGn.
+
+	  minargs enables enforcing of minimum count of arguments to pass to
+	  func_odbc, so if you're unconditionally using ARG1 through ARG4 then
+	  this should be set to 4.  func_odbc will generate an error in this case,
+	  so for example
+
+	  [FOO]
+	  minargs = 4
+
+	  and ODBC_FOO(a,b,c) in dialplan will now error out instead of using a
+	  potentially leaked ARG4 from Gosub().
+
+	  ARGC is needed if you're using optional argument, to verify whether or
+	  not an argument has been passed, else it's possible to use a leaked ARGn
+	  from Gosub (app_stack).  So now you can safely do
+	  ${IF($[${ARGC}>3]?${ARGV}:default value)} kind of thing.
+
+	  Change-Id: I6ca0b137d90b03f6aa9c496991f6cbf1518f6c24
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+2021-01-13 14:05 +0000 [6e695c867f]  Sebastien Duthil <sduthil@wazo.community>
+
+	* app_mixmonitor: Add AMI events MixMonitorStart, -Stop and -Mute.
+
+	  ASTERISK-29244
+
+	  Change-Id: I1862d58264c2c8b5d8983272cb29734b184d67c5
+
+2021-02-01 15:24 +0000 [5e998d8bd3]  Kevin Harwell <kharwell@sangoma.com>
+
+	* AST-2021-002: Remote crash possible when negotiating T.38
+
+	  When an endpoint requests to re-negotiate for fax and the incoming
+	  re-invite is received prior to Asterisk sending out the 200 OK for
+	  the initial invite the re-invite gets delayed. When Asterisk does
+	  finally send the re-inivite the SDP includes streams for both audio
+	  and T.38.
+
+	  This happens because when the pending topology and active topologies
+	  differ (pending stream is not in the active) in the delayed scenario
+	  the pending stream is appended to the active topology. However, in
+	  the fax case the pending stream should replace the active.
+
+	  This patch makes it so when a delay occurs during fax negotiation,
+	  to or from, the audio stream is replaced by the T.38 stream, or vice
+	  versa instead of being appended.
+
+	  Further when Asterisk sent the re-invite with both audio and T.38,
+	  and the endpoint responded with a declined T.38 stream then Asterisk
+	  would crash when attempting to change the T.38 state.
+
+	  This patch also puts in a check that ensures the media state has a
+	  valid fax session (associated udptl object) before changing the
+	  T.38 state internally.
+
+	  ASTERISK-29203 #close
+
+	  Change-Id: I407f4fa58651255b6a9030d34fd6578cf65ccf09
+
+2021-01-26 11:09 +0000 [389b8b0774]  Alexander Traud <pabstraud@compuserve.com>
+
+	* rtp:  Enable srtp replay protection
+
+	  Add option "srtpreplayprotection" rtp.conf to enable srtp
+	  replay protection.
+
+	  ASTERISK-29260
+	  Reported by: Alexander Traud
+
+	  Change-Id: I5cd346e3c6b6812039d1901aa4b7be688173b458
+
+2020-12-28 06:43 +0000 [7d15655f9d]  Ivan Poddubnyi <ivan.poddubny@gmail.com>
+
+	* res_pjsip_diversion: Fix adding more than one histinfo to Supported
+
+	  New responses sent within a PJSIP sessions are based on those that were
+	  sent before. Therefore, adding/modifying a header once causes it to be
+	  sent on all responses that follow.
+
+	  Sending 181 Call Is Being Forwarded many times first adds "histinfo"
+	  duplicated more and more, and eventually overflows past the array
+	  boundary.
+
+	  This commit adds a check preventing adding "histinfo" more than once,
+	  and skipping it if there is no more space in the header.
+
+	  Similar overflow situations can also occur in res_pjsip_path and
+	  res_pjsip_outbound_registration so those were also modified to
+	  check the bounds and suppress duplicate Supported values.
+
+	  ASTERISK-29227
+	  Reported by: Ivan Poddubny
+
+	  Change-Id: Id43704a1f1a0293e35cc7f844026f0b04f2ac322
+
+2020-12-11 14:49 +0000 [e7b13df394]  Sean Bright <sean.bright@gmail.com>
+
+	* res_rtp_asterisk.c: Fix signed mismatch that leads to overflow
+
+	  ASTERISK-29205 #close
+
+	  Change-Id: Ib7aa65644e8df76e2378d7613ee7cf751b9d0bea
+
+2021-02-05 05:26 +0000 [492945ac60]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* pjsip: Make modify_local_offer2 tolerate previous failed SDP.
+
+	  If a remote side is broken and sends an SDP that can not be
+	  negotiated the call will be torn down but there is a window
+	  where a second 183 Session Progress or 200 OK that is forked
+	  can be received that also attempts to negotiate SDP. Since
+	  the code marked the SDP negotiation as being done and complete
+	  prior to this it assumes that there is an active local and remote
+	  SDP which it can modify, while in fact there is not as the SDP
+	  did not successfully negotiate. Since there is no local or remote
+	  SDP a crash occurs.
+
+	  This patch changes the pjmedia_sdp_neg_modify_local_offer2
+	  function to no longer assume that a previous SDP negotiation
+	  was successful.
+
+	  ASTERISK-29196
+
+	  Change-Id: I22de45916d3b05fdc2a67da92b3a38271ee5949e
+
+2021-02-09 11:25 +0000 [15b4080679]  George Joseph <gjoseph@digium.com>
+
+	* res_pjsip_refer: Always serialize calls to refer_progress_notify
+
+	  refer_progress_notify wasn't always being called from the progress
+	  serializer.  This could allow clearing notification->progress->sub
+	  in one thread while another was trying to use it.
+
+	  * Instances where refer_progress_notify was being called in-line,
+	    have been changed to use ast_sip_push_task().
+
+	  Change-Id: Idcf1934c4e873f2c82e2d106f8d9f040caf9fa1e
+
+2021-01-11 14:20 +0000 [00b229c69c]  Ben Ford <bford@digium.com>
+
+	* core_unreal: Fix T.38 faxing when using local channels.
+
+	  After some changes to streams and topologies, receiving fax through
+	  local channels stopped working. This change adds a stream topology with
+	  a stream of type IMAGE to the local channel pair and allows fax to be
+	  received.
+
+	  ASTERISK-29035 #close
+
+	  Change-Id: Id103cc5c9295295d8e68d5628e76220f8f17e9fb
+
+2021-02-02 02:33 +0000 [a96eb6de6c]  Boris P. Korzun <drtr0jan@yandex.ru>
+
+	* format_wav: Support of MIME-type for wav16
+
+	  Provided a support of a MIME-type for wav16. Added new MIME-type
+	  for classic wav.
+
+	  ASTERISK-29275 #close
+
+	  Change-Id: I749bda287ba1ab20c1e0af5e4c0153817d47873b
+
+2021-02-05 02:33 +0000 [1f77c33c02]  Alexander Traud <pabstraud@compuserve.com>
+
+	* chan_sip: Allow [peer] without audio (text+video).
+
+	  Two previous commits, 620d9f4 and 6d980de, allow to set up a call
+	  without audio, again. That was introduced originally with commit f04d5fb
+	  but changed and broke over time. The original commit missed one
+	  scenario: A [peer] section in sip.conf, which does not allow audio at
+	  all. In that case, chan_sip rejected the call, although even when the
+	  requester offered no audio. Now, chan_sip does not check whether there
+	  is no audio format but checks whether there is no format in general. In
+	  other words, if there is at least one format to offer, the call succeeds.
+
+	  However, to prevent calls with no-audio, chan_sip still rejects calls
+	  when both call parties (caller = requester of the call *and* callee =
+	  [peer] section in sip.conf) included audio. In such a case, it is
+	  expected that the call should have audio.
+
+	  ASTERISK-29280
+
+	  Change-Id: I0fb74faf51ef22a60c10b467df6a4d1c1943b73e
+
+2021-01-28 12:02 +0000 [91b0778791]  George Joseph <gjoseph@digium.com>
+
+	* chan_iax2.c: Require secret and auth method if encryption is enabled
+
+	  If there's no secret specified for an iax2 peer and there's no secret
+	  specified in the dial string, Asterisk will crash if the auth method
+	  requested by the peer is MD5 or plaintext.  You also couldn't specify
+	  a default auth method in the [general] section of iax.conf so if you
+	  don't have static peers defined and just use the dial string, Asterisk
+	  will still crash even if you have a secret specified in the dial string.
+
+	  * Added logic to iax2_call() and authenticate_reply() to print
+	    a warning and hanhup the call if encryption is requested and
+	    there's no secret or auth method.  This prevents the crash.
+
+	  * Added the ability to specify a default "auth" in the [general]
+	    section of iax.conf.
+
+	  ASTERISK-29624
+	  Reported by: N A
+
+	  Change-Id: I5928e16137581f7d383fcc7fa04ad96c919e6254
+
+2021-02-03 12:53 +0000 [4a71b08091]  Sean Bright <sean.bright@gmail.com>
+
+	* app_read: Release tone zone reference on early return.
+
+	  Change-Id: I350939f2220f9e5d44ddf4c8d9a4c99fde4d169a
+
+2021-01-27 11:42 +0000 [620d9f4782]  Alexander Traud <pabstraud@compuserve.com>
+
+	* chan_sip: Set up calls without audio (text+video), again.
+
+	  The previous commit 6d980de fixed this issue in the core of Asterisk.
+	  With that, each channel technology can be used without audio
+	  theoretically. Practically, the channel-technology driver chan_sip
+	  turned out to have an invalid check preventing that. chan_sip tested
+	  whether there is at least one audio format. However, chan_sip has to
+	  test whether there is at least one format. More cannot be tested while
+	  requesting chan_sip because only the [general] capabilities but not the
+	  [peer] caps are known yet. And the [peer] caps might not be a subset or
+	  show any intersection with the [general] caps. This change here fixes
+	  this.
+
+	  The original commit f04d5fb, thirteen years ago, contained a software
+	  bug as it passed ANY audio capability to the channel-technology driver.
+	  Instead, it should have passed NO audio format. Therefore, this
+	  addressed issue here was not noticed in Asterisk 1.6.x and Asterisk 1.8.
+	  Then, Asterisk 10 changed that from ANY to NO, but nobody reported since
+	  then.
+
+	  ASTERISK-29265
+
+	  Change-Id: Ic16a3bf13cd1b5c4fc4041ed74961177d96b600f
+
+2021-01-22 09:12 +0000 [55891227e8]  Dan Cropp <dan@amtelco.com>
+
+	* chan_pjsip, app_transfer: Add TRANSFERSTATUSPROTOCOL variable
+
+	  When a Transfer/REFER is executed, TRANSFERSTATUSPROTOCOL variable is
+	  0 when no protocl specific error
+	  SIP example of failure, 3xx-6xx for the SIP error code received
+
+	  This allows applications to perform actions based on the failure
+	  reason.
+
+	  ASTERISK-29252 #close
+	  Reported-by: Dan Cropp
+
+	  Change-Id: Ia6a94784b4925628af122409cdd733c9f29abfc4
+
+2021-01-22 02:54 +0000 [6d980de282]  Alexander Traud <pabstraud@compuserve.com>
+
+	* channel: Set up calls without audio (text+video), again.
+
+	  ASTERISK-29259
+
+	  Change-Id: Ib6a6550e0e08355745d66da8e60ef49e81f9c6c5
+
+2021-01-22 07:12 +0000 [9b5d20e3d5]  Mark Petersen <bugs.digium.com@zombie.dk>
+
+	* res/res_pjsip.c: allow user=phone when number contain *#
+
+	  if From number contain * or # asterisk will not add user=phone
+
+	  Currently only number that uses AST_DIGIT_ANYNUM can have "user=phone" but the validation should use AST_DIGIT_ANY
+	  this is a problem when you want to send call to ISUP
+	  as they will disregard the From header and either replace From with anonymous or with p-asserted-identity
+
+	  ASTERISK-29261
+	  Reported by: Mark Petersen
+	  Tested by: Mark Petersen
+
+	  Change-Id: I3307bdbf757582740bfee4110e85f7b6c9291cc4
+
+2021-01-21 13:28 +0000 [4aff42b274]  Alexander Traud <pabstraud@compuserve.com>
+
+	* chan_sip: SDP: Reject audio streams correctly.
+
+	  This completes the fix for ASTERISK_24543. Only when the call is an
+	  outgoing call, consult and append the configured format capabilities
+	  (p->caps). When all audio formats got rejected the negotiated format
+	  capabilities (p->jointcaps) contain no audio formats for incoming
+	  calls. This is required when there are other accepted media streams.
+
+	  ASTERISK-29258
+
+	  Change-Id: I8bab31c7f3f3700dce204b429ad238a524efebb9
+
+2021-01-22 11:17 +0000 [05472da92b]  Ivan Poddubnyi <ivan.poddubny@gmail.com>
+
+	* main/frame: Add missing control frame names to ast_frame_subclass2str
+
+	  Log proper control frame names instead of "Unknown control '14'", etc.
+
+	  Change-Id: I1724f2f4d1b064b25a5c93a7da0cb03be5143935
+
+2021-01-23 07:15 +0000 [92f5cf7f2d]  Boris P. Korzun <drtr0jan@yandex.ru>
+
+	* res_musiconhold: Add support of various URL-schemes by MoH.
+
+	  Provided a support of variuos URL-schemes for res_musiconhold,
+	  registered by ast_bucket_scheme_register().
+
+	  ASTERISK-29262 #close
+
+	  Change-Id: If0ea8697587353dce358a70035d82649fd4632b6
+
+2021-01-08 10:02 +0000 [060ce10163]  Jaco Kroon <jaco@uls.co.za>
+
+	* AC_HEADER_STDC causes a compile failure with autoconf 2.70
+
+	  From https://www.mail-archive.com/bug-autoconf@gnu.org/msg04408.html
+
+	  > ... the long-obsolete AC_HEADER_STDC, previously used internally by
+	  > AC_INCLUDES_DEFAULT, used AC_EGREP_HEADER.  The AC_HEADER_STDC macro
+	  > is now a no-op (and is not used at all within Autoconf anymore), so
+	  > that change is likely what made the first use of AC_EGREP_HEADER the
+	  > one inside the if condition, causing the observed results.
+
+	  The implication is that the test does nothing anyway, and due to it
+	  being a no-op from 2.70 onwards, results in the required not being set
+	  to yes, resulting in ./configure to fail.
+
+	  Change-Id: Ic1ff38d87f791fbf1f2a80512f81bb7110392460
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+2021-01-15 03:33 +0000 [10a0a0c59b]  Alexander Traud <pabstraud@compuserve.com>
+
+	* pjsip_scheduler: Fix pjsip show scheduled_tasks like for compiler Clang.
+
+	  Otherwise, Clang 10 warned because of logical-not-parentheses.
+
+	  Change-Id: Ia8fb493f727b08070eb2dcf520c08df34ed11d79
+
+2021-01-15 05:09 +0000 [df6afadf26]  Alexander Traud <pabstraud@compuserve.com>
+
+	* res_pjsip_session: Avoid sometimes-uninitialized warning with Clang.
+
+	  ASTERISK-29248
+
+	  Change-Id: I2b17bd5ffb246bc64c463402c9831413da78a556
+
+2021-01-14 08:47 +0000 [6d2bec7028]  Sean Bright <sean.bright@gmail.com>
+
+	* res_pjsip_pubsub: Fix truncation of persisted SUBSCRIBE packet
+
+	  The last argument to ast_copy_string() is the buffer size, not the
+	  number of characters, so we add 1 to avoid stamping out the final \n
+	  in the persisted SUBSCRIBE message.
+
+	  Change-Id: I019b78942836f57965299af15d173911fcead5b2
+
+2021-01-11 14:25 +0000 [948ceb1228]  Ben Ford <bford@digium.com>
+
+	* chan_pjsip.c: Add parameters to frame in indicate.
+
+	  There are a couple of parameters (datalen and data) that do not get set
+	  in chan_pjsip_indicate which could cause an Invalid message to pop up
+	  for things such as fax. This patch adds them to the frame.
+
+	  Change-Id: Ia51be086a0708be905e73d1f433572c49c7e38f8
+
+2020-12-22 04:42 +0000 [24e678b9bb]  Robert Cripps <rcripps@voxbone.com>
+
+	* res/res_pjsip_session.c: Check that media type matches in
+	  function ast_sip_session_media_state_add.
+
+	  Check ast_media_type matches when a ast_sip_session_media is found
+	  otherwise when transitioning from say image to audio, the wrong
+	  session is returned in the first if statement.
+
+	  ASTERISK-29220 #close
+
+	  Change-Id: I6f6efa9b821ebe8881bb4c8c957f8802ddcb4b5d
+
+2020-12-30 07:56 +0000 [c559667868]  Jean Aunis <jean.aunis@prescom.fr>
+
+	* Stasis/messaging: tech subscriptions conflict with endpoint subscriptions.
+
+	  When both a tech subscription and an endpoint subscription exist for a given
+	  endpoint, TextMessageReceived events are dispatched to the tech subscription
+	  only.
+
+	  ASTERISK-29229
+
+	  Change-Id: I9eac4cba5f9e27285a282509395347abc58fc2b8
+
+2020-12-23 08:44 +0000 [1c05667cfc]  Alexander Traud <pabstraud@compuserve.com>
+
+	* chan_sip: SDP: Sidestep stream parsing when its media is disabled.
+
+	  Previously, chan_sip parsed all known media streams in an SDP offer
+	  like video (and text) even when videosupport=no (and textsupport=no).
+	  This wasted processor power. Furthermore, chan_sip accepted SDP offers,
+	  including no audio but just video (or text) streams although
+	  videosupport=no (or textsupport=no). Finally, chan_sip denied the whole
+	  offer instead of individual streams when they had encryption (SDES-sRTP)
+	  unexpectedly enabled.
+
+	  ASTERISK-29238
+	  ASTERISK-29237
+	  ASTERISK-29222
+
+	  Change-Id: Ie49e4e2a11f0265f914b684738348ba8c0f89755
+
+2020-12-29 12:16 +0000 [f2aa6c7017]  Ivan Poddubnyi <ivan.poddubny@gmail.com>
+
+	* chan_pjsip: Assign SIPDOMAIN after creating a channel
+
+	  session->channel doesn't exist until chan_pjsip creates it, so intead of
+	  setting a channel variable every new incoming call sets one and the same
+	  global variable.
+
+	  This patch moves the code to chan_pjsip so that SIPDOMAIN is set on
+	  a newly created channel, it also removes a misleading reference to
+	  channel->session used to fetch call pickup configuraion.
+
+	  ASTERISK-29240
+
+	  Change-Id: I90c9bbbed01f5d8863585631a29322ae4e046755
+
+2020-12-31 05:53 +0000 [134d2e729d]  Ivan Poddubnyi <ivan.poddubny@gmail.com>
+
+	* chan_pjsip: Stop queueing control frames twice on outgoing channels
+
+	  The fix for ASTERISK-27902 made chan_pjsip process SIP responses twice.
+	  This resulted in extra noise in logs (for example, "is making progress"
+	  and "is ringing" get logged twice by app_dial), as well as in noise in
+	  signalling: one incoming 183 Session Progress results in 2 outgoing 183-s.
+
+	  This change splits the response handler into 2 functions:
+	   - one for updating HANGUPCAUSE, which is still called twice,
+	   - another that does the rest, which is called only once as before.
+
+	  ASTERISK-28016
+	  Reported-by: Alex Hermann
+
+	  ASTERISK-28549
+	  Reported-by: Gant Liu
+
+	  ASTERISK-28185
+	  Reported-by: Julien
+
+	  Change-Id: I0a1874be5bb5ed12d572d17c7f80de6e5e542940
+
+2020-12-18 13:06 +0000 [2d3441772b]  Jaco Kroon <jaco@uls.co.za>
+
+	* contrib/systemd: Added note on common issues with systemd and asterisk
+
+	  With newer version of linux /var/run/ is a symlink to /run/ that has
+	  been turned into tmpfs.
+
+	  Added note that if asterisk has to bind to a specific IP that
+	  systemd has to wait until the network is up.
+
+	  Added note on how to make sure that the environment variable
+	  HOSTNAME is included.
+
+	  ASTERISK-29216
+	  Reported by: Mark Petersen
+	  Tested by: Mark Petersen
+
+	  Change-Id: Ib3e560655befd3e99eec743687144f5569533379
+
+2021-01-07 08:40 +0000 [9a4486e9fb]  George Joseph <gjoseph@digium.com>
+
+	* Revert "res_pjsip_outbound_registration.c:  Use our own scheduler and other stuff"
+
+	  This reverts commit 2fe76dd816706f045ecbc44bf8ad6498977415b3.
+
+	  Reason for revert: Too many issues reported.  Need to research and correct.
+
+	  ASTERISK-29230
+	  ASTERISK-29231
+	  Reported by: Michael Maier
+
+	  Change-Id: I6453af680e17d8ffe7af2c5de7e1b2a58c8793cb
+
+2020-12-18 13:06 +0000 [c797500956]  Jaco Kroon <jaco@uls.co.za>
+
+	* func_lock: fix multiple-channel-grant problems.
+
+	  Under contention it becomes possible that multiple channels will be told
+	  they successfully obtained the lock, which is a bug.  Please refer
+
+	  ASTERISK-29217
+
+	  This introduces a couple of changes.
+
+	  1.  Replaces requesters ao2 container with simple counter (we don't
+	      really care who is waiting for the lock, only how many).  This is
+	      updated undex ->mutex to prevent memory access races.
+	  2.  Correct semantics for ast_cond_timedwait() as described in
+	      pthread_cond_broadcast(3P) is used (multiple threads can be released
+	      on a single _signal()).
+	  3.  Module unload races are taken care of and memory properly cleaned
+	      up.
+
+	  Change-Id: I6f68b5ec82ff25b2909daf6e4d19ca864a463e29
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+2020-12-23 11:41 +0000 [4e038c1eaa]  Jaco Kroon <jaco@uls.co.za>
+
+	* pbx_lua:  Add LUA_VERSIONS environment variable to ./configure.
+
+	  On Gentoo it's possible to have multiple lua versions installed, all
+	  with a path of /usr, so it's not possible to use the current --with-lua
+	  option to determisticly pin to a specific version as is required by the
+	  Gentoo PMS standards.
+
+	  This environment variable allows to lock to specific versions,
+	  unversioned check will be skipped if this variable is supplied.
+
+	  Change-Id: I8c403eda05df25ee0193960262ce849c7d2fd088
+	  Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+2020-12-23 13:06 +0000 [3bcf483373]  Kevin Harwell <kharwell@sangoma.com>
+
+	* app_mixmonitor: cleanup datastore when monitor thread fails to launch
+
+	  launch_monitor_thread is responsible for creating and initializing
+	  the mixmonitor, and dependent data structures. There was one off
+	  nominal path after the datastore gets created that triggers when
+	  the channel being monitored is hung up prior to monitor starting
+	  itself.
+
+	  If this happened the monitor thread would not "launch", and the
+	  mixmonitor object and associated objects are freed, including the
+	  underlying datastore data object. However, the datastore itself was
+	  not removed from the channel, so when the channel eventually gets
+	  destroyed it tries to access the previously freed datastore data
+	  and crashes.
+
+	  This patch removes and frees datastore object itself from the channel
+	  before freeing the mixmonitor object thus ensuring the channel does
+	  not call it when destroyed.
+
+	  ASTERISK-28947 #close
+
+	  Change-Id: Id4f9e958956d62473ed5ff06c98ae3436e839ff8
+
+2020-12-24 09:03 +0000 [44d68bd56b]  Sean Bright <sean.bright@gmail.com>
+
+	* app_voicemail: Prevent deadlocks when out of ODBC database connections
+
+	  ASTERISK-28992 #close
+
+	  Change-Id: Ia7d608924036139ee2520b840d077762d02668d0
+
+2020-12-07 16:59 +0000 [ffa87ecade]  Dan Cropp <dan@amtelco.com>
+
+	* chan_pjsip: Incorporate channel reference count into transfer_refer().
+
+	  Add channel reference count for PJSIP REFER. The call could be terminated
+	  prior to the result of the transfer. In that scenario, when the SUBSCRIBE/NOTIFY
+	  occurred several minutes later, it would attempt to access a session which was
+	  no longer valid.  Terminate event subscription if pjsip_xfer_initiate() or
+	  pjsip_xfer_send_request() fails in transfer_refer().
+
+	  ASTERISK-29201 #close
+	  Reported-by: Dan Cropp
+
+	  Change-Id: I3fd92fd14b4e3844d3d7b0f60fe417a4df5f2435
+
+2020-12-22 17:40 +0000 [4274a4a7dd]  Kevin Harwell <kharwell@sangoma.com>
+
+	* pbx_realtime: wrong type stored on publish of ast_channel_snapshot_type
+
+	  A prior patch segmented channel snapshots, and changed the underlying
+	  data object type associated with ast_channel_snapshot_type stasis
+	  messages. Prior to Asterisk 18 it was a type ast_channel_snapshot, but
+	  now it type ast_channel_snapshot_update.
+
+	  When publishing ast_channel_snapshot_type in pbx_realtime the
+	  ast_channel_snapshot was being passed in as the message data
+	  object. When a handler, expecting a data object type of
+	  ast_channel_snapshot_update, dereferenced this value a crash
+	  would occur.
+
+	  This patch makes it so pbx_realtime now uses the expected type, and
+	  channel snapshot publish method when publishing.
+
+	  ASTERISK-29168 #close
+
+	  Change-Id: I9a2cfa0ec285169317f4b9146e4027da8a4fe896
+
+2020-12-18 09:16 +0000 [1b74555fcf]  Sean Bright <sean.bright@gmail.com>
+
+	* asterisk: Export additional manager functions
+
+	  Rename check_manager_enabled() and check_webmanager_enabled() to begin
+	  with ast_ so that the symbols are automatically exported by the
+	  linker.
+
+	  ASTERISK~29184
+
+	  Change-Id: I85762b9a5d14500c15f6bad6507138c8858644c9
+
+2020-12-19 11:54 +0000 [505939c9ed]  Nick French <nickfrench@gmail.com>
+
+	* res_pjsip: Prevent segfault in UDP registration with flow transports
+
+	  Segfault occurs during outbound UDP registration when all
+	  transport states are being iterated over. The transport object
+	  in the transport is accessed, but flow transports have a NULL
+	  transport object.
+
+	  Modify to not iterate over any flow transport
+
+	  ASTERISK-29210 #close
+
+	  Change-Id: If28dc3a18bdcbd0a49598b09b7fe4404d45c996a
+
+2020-12-01 08:11 +0000 [80c14f74bc]  Alexander Traud <pabstraud@compuserve.com>
+
+	* codecs: Remove test-law.
+
+	  This was dead code, test code introduced with Asterisk 13. This was
+	  found while analyzing ASTERISK_28416 and ASTERISK_29185. This change
+	  partly fixes, not closes those two issues.
+
+	  Change-Id: I42d0daa37f6f334c7d86672f06f085858a3f3940
+
+2020-12-22 02:58 +0000 [51e2187a14]  Torrey Searle <tsearle@voxbone.com>
+
+	* res/res_pjsip_diversion: prevent crash on tel: uri in History-Info
+
+	  Add a check to see if the URI is a Tel URI and prevent crashing on
+	  trying to retrieve the reason parameter.
+
+	  ASTERISK-29191
+	  ASTERISK-29219
+
+	  Change-Id: I0320aa205f22cda511d60a2edf2b037e8fd6cc37
+	  (cherry picked from commit a7aea71e60d513af82c6e3825e2308e063139b63)
+
+2020-12-26 12:14 +0000 [058bc0d593]  Richard Mudgett <rmudgett@digium.com>
+
+	* chan_vpb.cc: Fix compile errors.
+
+	  Fix the usual compile problem when someone adds a new callback to struct
+	  ast_channel_tech.
+
+	  Change-Id: I9bdeb8a8cc65f03b2d6e4f2eb5809af47c906c32
+
+2020-12-26 11:42 +0000 [6d7af72559]  Richard Mudgett <rmudgett@digium.com>
+
+	* res_pjsip_session.c: Fix compiler warnings.
+
+	  AST_VECTOR_SIZE() returns a size_t.  This is not always equivalent to an
+	  unsigned long on all machines.
+
+	  Change-Id: I0a4189a104e6e3a2e2273de06620eaef19df9338
+
+2020-12-13 06:03 +0000 [02c4b2ac60]  Sungtae Kim <pchero21@gmail.com>
+
+	* res_pjsip_session: Fixed NULL active media topology handle
+
+	  Added NULL pointer check to prevent Asterisk crash.
+
+	  ASTERISK-29215
+
+	  Change-Id: If07e50ea8d78cb610af9195fc13b5dca4bfcef95
+
+2020-12-11 13:27 +0000 [357510cec3]  Sean Bright <sean.bright@gmail.com>
+
+	* app_chanspy: Spyee information missing in ChanSpyStop AMI Event
+
+	  The documentation in the wiki says there should be spyee-channel
+	  information elements in the ChanSpyStop AMI event.
+
+	      https://wiki.asterisk.org/wiki/x/Xc5uAg
+
+	  However, this is not the case in Asterisk <= 16.10.0 Version. We're
+	  using these Spyee* arguments since Asterisk 11.x, so these arguments
+	  vanished in Asterisk 12 or higher.
+
+	  For maximum compatibility, we still send the ChanSpyStop event even if
+	  we are not able to find any 'Spyee' information.
+
+	  ASTERISK-28883 #close
+
+	  Change-Id: I81ce397a3fd614c094d043ffe5b1b1d76188835f
+
+2020-11-30 19:27 +0000 [91fc57f56b]  Sungtae Kim <pchero21@gmail.com>
+
+	* res_ari: Fix wrong media uri handle for channel play
+
+	  Fixed wrong null object handle in
+	  /channels/<channel_id>/play request handler.
+
+	  ASTERISK-29188
+
+	  Change-Id: I6691c640247a51ad15f23e4a203ca8430809bafe
+
+2020-12-10 09:09 +0000 [7d4ae7dc18]  George Joseph <gjoseph@digium.com>
+
+	* logger.c: Automatically add a newline to formats that don't have one
+
+	  Scope tracing allows you to not specify a format string or variable,
+	  in which case it just prints the indent, file, function, and line
+	  number.  The trace output automatically adds a newline to the end
+	  in this case.  If you also have debugging turned on for the module,
+	  a debug message is also printed but the standard log functionality
+	  which prints it doesn't add the newline so you have messages
+	  that don't break correctly.
+
+	   * format_log_message_ap(), which is the common log
+	     message formatter for all channels, now adds a
+	     newline to the end of format strings that don't
+	     already have a newline.
+
+	  ASTERISK-29209
+	  Reported by: Alexander Traud
+
+	  Change-Id: I994a7df27f88df343b7d19f3e81a4b562d9d41da
+
+2020-12-08 11:37 +0000 [0b10995811]  Pirmin Walthert <infos@nappsoft.ch>
+
+	* res_pjsip_nat.c: Create deep copies of strings when appropriate
+
+	  In rewrite_uri asterisk was not making deep copies of strings when
+	  changing the uri. This was in some cases causing garbage in the route
+	  header and in other cases even crashing asterisk when receiving a
+	  message with a record-route header set. Thanks to Ralf Kubis for
+	  pointing out why this happens. A similar problem was found in
+	  res_pjsip_transport_websocket.c. Pjproject needs as well to be patched
+	  to avoid garbage in CANCEL messages.
+
+	  ASTERISK-29024 #close
+
+	  Change-Id: Ic5acd7fa2fbda3080f5f36ef12e46804939b198b
+
+2020-12-10 17:06 +0000 [5e426987c2]  Nathan Bruning <nathan@iperity.com>
+
+	* res_musiconhold: Don't crash when real-time doesn't return any entries
+
+	  ASTERISK-29211 #close
+
+	  Change-Id: Ifbf0a4f786ab2a52342f9d1a1db4c9907f069877
+
+2020-12-16 06:17 +0000 [9ee1f7154f]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_pjsip_pidf_digium_body_supplement: Support Sangoma user agent.
+
+	  This adds support for both Digium and Sangoma user agent strings
+	  for the Sangoma specific body supplement.
+
+	  Change-Id: Ib99362b24b91d3cbe888d8b2fce3fad5515d9482
+
+2020-10-29 12:21 +0000 [6475fe3dd7]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* pjsip: Match lifetime of INVITE session to our session.
+
+	  In some circumstances it was possible for an INVITE
+	  session to be destroyed while we were still using it.
+	  This occurred due to the reference on the INVITE session
+	  being released internally as a result of its state
+	  changing to DISCONNECTED.
+
+	  This change adds a reference to the INVITE session
+	  which is released when our own session is destroyed,
+	  ensuring that the INVITE session remains valid for
+	  the lifetime of our session.
+
+	  ASTERISK-29022
+
+	  Change-Id: I300c6d9005ff0e6efbe1132daefc7e47ca6228c9
+
+2020-11-21 11:51 +0000 [90fd1fd96a]  Sean Bright <sean.bright@gmail.com>
+
+	* res_http_media_cache.c: Set reasonable number of redirects
+
+	  By default libcurl does not follow redirects, so we explicitly enable
+	  it by setting CURLOPT_FOLLOWLOCATION. Once that is enabled, libcurl
+	  will follow up to CURLOPT_MAXREDIRS redirects, which by default is
+	  configured to be unlimited.
+
+	  This patch sets CURLOPT_MAXREDIRS to a more reasonable default (8). If
+	  we determine at some point that this needs to be increased on
+	  configurable it is a trivial change.
+
+	  ASTERISK-29173 #close
+
+	  Change-Id: I4925ebbcf0c7d728bb9252b3795b3479ae225b30
+
+2020-10-29 06:25 +0000 [b08427134f]  laszlovl <digium@lvlconsultancy.nl>
+
+	* Introduce astcachedir, to be used for temporary bucket files
+
+	  As described in the issue, /tmp is not a suitable location for a
+	  large amount of cached media files, since most distributions make
+	  /tmp a RAM-based tmpfs mount with limited capacity.
+
+	  I opted for a location that can be configured separately, as opposed
+	  to using a subdirectory of spooldir, given the different storage
+	  profile (transient files vs files that might stay there indefinitely).
+
+	  This commit just makes the cache directory configurable, and changes
+	  the default location from /tmp to /var/cache/asterisk.
+
+	  ASTERISK-29143
+
+	  Change-Id: Ic54e95199405abacd9e509cef5f08fa14c510b5d
+
+2020-11-23 14:56 +0000 [c8b6340023]  Sean Bright <sean.bright@gmail.com>
+
+	* media_cache: Fix reference leak with bucket file metadata
+
+	  Change-Id: Ia0e4124110df613ce5fdfa9ef8780016ebaa52c6
+
+2020-11-24 00:55 +0000 [ab7a08b4ef]  Stanislav <stas.abramenkov@gmail.com>
+
+	* res_pjsip_stir_shaken: Fix module description
+
+	  the 'J' is missing in module description.
 	  "PSIP STIR/SHAKEN Module for Asterisk" -> "PJSIP STIR/SHAKEN Module for Asterisk"
 
-	  ASTERISK-29175 #close
+	  ASTERISK-29175 #close
+
+	  Change-Id: I17da008540ee2e8496b644d05f995b320b54ad7a
+
+2020-10-12 05:30 +0000 [eda3679c1c]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* voicemail: add option 'e' to play greetings as early media
+
+	  When using this option, answering the channel is deferred until
+	  all prompts/greetings have been played and the caller is about
+	  to leave their message.
+
+	  ASTERISK-29118 #close
+
+	  Change-Id: I41b9f0428783c0bd697c8c994f906d1e75ce9ddb
+
+2020-11-02 01:24 +0000 [b91fb3c396]  Alexander Traud <pabstraud@compuserve.com>
+
+	* loader: Sync load- and build-time deps.
+
+	  In MODULEINFO, each depend has to be listed in .requires of AST_MODULE_INFO.
+
+	  ASTERISK-29148
+
+	  Change-Id: I254dd33194ae38d2877b8021c57c2a5deb6bbcd2
+
+2020-11-18 13:11 +0000 [d04b5903d1]  Sean Bright <sean.bright@gmail.com>
+
+	* CHANGES: Remove already applied CHANGES update
+
+	  Change-Id: Iee7163bc732d58c5cbaa2cfab1f5aab4a412060a
+
+2020-11-17 14:19 +0000 [fba10fb54c]  Alexander Greiner-Baer <alex+asterisk@greiner-baer.de>
+
+	* res_pjsip: set Accept-Encoding to identity in OPTIONS response
+
+
+
+	  RFC 3261 says that the Accept-Encoding header should be present
+	  in an options response. Permitted values according to RFC 2616
+	  are only compression algorithms like gzip or the default identity
+	  encoding. Therefore "text/plain" is not a correct value here.
+	  As long as the header is hard coded, it should be set to "identity".
+
+	  Without this fix an Alcatel OmniPCX periodically logs warnings like
+	  "[sip_acceptIncorrectHeader] Header Accept-Encoding is malformed"
+	  on a SIP Trunk.
+
+	  ASTERISK-29165 #close
+
+	  Change-Id: I0aa2211ebf0b4c2ed554ac7cda794523803a3840
+
+2020-11-04 07:39 +0000 [103d7da3bb]  Alexander Traud <pabstraud@compuserve.com>
+
+	* chan_sip: Remove unused sip_socket->port.
+
+	  12 years ago, with ASTERISK_12115 the last four get/uses of socket.port
+	  vanished. However, the struct member itself and all seven set/uses
+	  remained as dead code.
+
+	  ASTERISK-28798
+
+	  Change-Id: Ib90516a49eca3d724a70191278aaf2144fb58c59
+
+2020-11-13 06:19 +0000 [8cb439f7e4]  Boris P. Korzun <drtr0jan@yandex.ru>
+
+	* bridge_basic: Fixed setup of recall channels
+
+	  Fixed a bug (like a typo) in retransfer_enter() at main/bridge_basic.c:2641.
+	  common_recall_channel_setup() setups common things on the recalled transfer
+	  target, but used same target as source instead trasfered.
+
+	  ASTERISK-29161 #close
+
+	  Change-Id: Ieb549654a621c38b1ad5e9d15b9f18823d9cc31f
+
+2020-11-03 02:27 +0000 [7c355d78cb]  Alexander Traud <pabstraud@compuserve.com>
+
+	* modules.conf: Align the comments for more conclusiveness.
+
+	  Change-Id: I79cc693cd5a6e5dd7d403b7e91d970ff1ddf8306
+
+2020-11-11 08:55 +0000 [73f458b1e0]  George Joseph <gjoseph@digium.com>
+
+	* app_queue: Fix deadlock between update and show queues
+
+	  Operations that update queues when shared_lastcall is set lock the
+	  queue in question, then have to lock the queues container to find the
+	  other queues with the same member. On the other hand, __queues_show
+	  (which is called by both the CLI and AMI) does the reverse. It locks
+	  the queues container, then iterates over the queues locking each in
+	  turn to display them.  This creates a deadlock.
+
+	  * Moved queue print logic from __queues_show to a separate function
+	    that can be called for a single queue.
+
+	  * Updated __queues_show so it doesn't need to lock or traverse
+	    the queues container to show a single queue.
+
+	  * Updated __queues_show to snap a copy of the queues container and iterate
+	    over that instead of locking the queues container and iterating over
+	    it while locked.  This prevents us from having to hold both the
+	    container lock and the queue locks at the same time.  This also
+	    allows us to sort the queue entries.
+
+	  ASTERISK-29155
+
+	  Change-Id: I78d4dc36728c2d7bc187b97d82673fc77f2bcf41
+
+2020-11-02 13:53 +0000 [2fe76dd816]  George Joseph <gjoseph@digium.com>
+
+	* res_pjsip_outbound_registration.c:  Use our own scheduler and other stuff
+
+	  * Instead of using the pjproject timer heap, we now use our own
+	    pjsip_scheduler.  This allows us to more easily debug and allows us to
+	    see times in "pjsip show/list registrations" as well as being able to
+	    see the registrations in "pjsip show scheduled_tasks".
+
+	  * Added the last registration time, registration interval, and the next
+	    registration time to the CLI output.
+
+	  * Removed calls to pjsip_regc_info() except where absolutely necessary.
+	    Most of the calls were just to get the server and client URIs for log
+	    messages so we now just save them on the client_state object when we
+	    create it.
+
+	  * Added log messages where needed and updated most of the existong ones
+	    to include the registration object name at the start of the message.
+
+	  Change-Id: I4534a0fc78c7cb69f23b7b449dda9748c90daca2
+
+2020-11-02 13:53 +0000 [5a4640d208]  George Joseph <gjoseph@digium.com>
+
+	* pjsip_scheduler.c: Add type ONESHOT and enhance cli show command
+
+	  * Added a ONESHOT type that never reschedules.
+
+	  * Added "like" capability to "pjsip show scheduled_tasks" so you can do
+	    the following:
+
+	    CLI> pjsip show scheduled_tasks like outreg
+	    PJSIP Scheduled Tasks:
+
+	    Task Name                                     Interval  Times Run ...
+	    ============================================= ========= ========= ...
+	    pjsip/outreg/testtrunk-reg-0-00000074            50.000   oneshot ...
+	    pjsip/outreg/voipms-reg-0-00000073              110.000   oneshot ...
+
+	  * Fixed incorrect display of "Next Start".
+
+	  * Compacted the displays of times in the CLI.
+
+	  * Added two new functions (ast_sip_sched_task_get_times2,
+	    ast_sip_sched_task_get_times_by_name2) that retrieve the interval,
+	    next start time, and next run time in addition to the times already
+	    returned by ast_sip_sched_task_get_times().
+
+	  Change-Id: Ie718ca9fd30490b8a167bedf6b0b06d619dc52f3
+
+2020-10-02 14:32 +0000 [cc7eb72f65]  Alexei Gradinari <alex2grad@gmail.com>
+
+	* sched: AST_SCHED_REPLACE_UNREF can lead to use after free of data
+
+	  The data can be freed if the old object '_data' is the same object as
+	  new 'data'. Because at first the object is unreferenced which can lead
+	  to destroying it.
+
+	  This could happened in res_pjsip_pubsub when the publication is updated
+	  which could lead to segfault in function publish_expire.
+
+	  Change-Id: I0164f57c387243510bdbd2f8dcf33377b6c202da
+
+2020-10-30 11:43 +0000 [b52acb87b0]  Alexander Traud <pabstraud@compuserve.com>
+
+	* res_pjsip/config_transport: Load and run without OpenSSL.
+
+	  ASTERISK-28933
+	  Reported-by: Walter Doekes
+
+	  Change-Id: I65eac49e5b0a79261ea80e2b9b38a836886ed59f
+
+2020-10-30 05:53 +0000 [64d2de19ee]  Alexander Traud <pabstraud@compuserve.com>
+
+	* res_stir_shaken: Include OpenSSL headers where used actually.
+
+	  This avoids the inclusion of the OpenSSL headers in the public header,
+	  which avoids one external library dependency in res_pjsip_stir_shaken.
+
+	  Change-Id: I6a07e2d81d2b5442e24e99b8cc733a99f881dcf4
+
+2020-10-18 13:40 +0000 [bc58e84f47]  Dovid Bender <dovid@telecurve.com>
+
+	* func_curl.c: Allow user to set what return codes constitute a failure.
+
+	  Currently any response from res_curl where we get an answer from the
+	  web server, regardless of what the response is (404, 403 etc.) Asterisk
+	  currently treats it as a success. This patch allows you to set which
+	  codes should be considered as a failure by Asterisk. If say we set
+	  failurecodes=404,403 then when using curl in realtime if a server gives
+	  a 404 error Asterisk will try to failover to the next option set in
+	  extconfig.conf
+
+	  ASTERISK-28825
+
+	  Reported by: Dovid Bender
+	  Code by: Gobinda Paul
+
+	  Change-Id: I94443e508343e0a3e535e51ea6e0562767639987
+
+2020-11-04 15:08 +0000 [b82f880647]  Kevin Harwell <kharwell@sangoma.com>
+
+	* AST-2020-001 - res_pjsip: Return dialog locked and referenced
+
+	  pjproject returns the dialog locked and with a reference. However,
+	  in Asterisk the method that handles this decrements the reference
+	  and removes the lock prior to returning. This makes it possible,
+	  under some circumstances, for another thread to free said dialog
+	  before the thread that created it attempts to use it again. Of
+	  course when the thread that created it tries to use a freed dialog
+	  a crash can occur.
+
+	  This patch makes it so Asterisk now returns the newly created
+	  dialog both locked, and with an added reference. This allows the
+	  caller to de-reference, and unlock the dialog when it is safe to
+	  do so.
+
+	  In the case of a new SIP Invite the lock, and reference are now
+	  held for the entirety of the new invite handling process.
+	  Otherwise it's possible for the dialog, or its dependent objects,
+	  like the transaction, to disappear. For example if there is a TCP
+	  transport error.
+
+	  ASTERISK-29057 #close
+
+	  Change-Id: I5ef645a47829596f402cf383dc02c629c618969e
+	  (cherry picked from commit 6baa4b53bef5d9c53692f22cf146215b42de1e89)
+
+2020-11-03 10:38 +0000 [cd8f8b94f8]  Ben Ford <bford@digium.com>
+
+	* AST-2020-002 - res_pjsip: Stop sending INVITEs after challenge limit.
+
+	  If Asterisk sends out and INVITE and receives a challenge with a
+	  different nonce value each time, it will continually send out INVITEs,
+	  even if the call is hung up. The endpoint must be configured for
+	  outbound authentication in order for this to occur. A limit has been set
+	  on outbound INVITEs so that, once reached, Asterisk will stop sending
+	  INVITEs and the transaction will terminate.
+
+	  ASTERISK-29013
+
+	  Change-Id: I2d001ca745b00ca8aa12030f2240cd72363b46f7
+
+2020-10-29 10:21 +0000 [a5d55fc9e1]  Sean Bright <sean.bright@gmail.com>
+
+	* sip_to_pjsip.py: Handle #include globs and other fixes
+
+	  * Wildcards in #includes are now properly expanded
+
+	  * Implement operators for Section class to allow sorting
+
+	  ASTERISK-29142 #close
+
+	  Change-Id: I9b9cd95f4cbe5c24506b75d17173c5aa1a83e5df
+
+2020-10-29 03:55 +0000 [57ee79a563]  Alexander Traud <pabstraud@compuserve.com>
+
+	* Compiler fixes for GCC with -Og
+
+	  ASTERISK-29144
+
+	  Change-Id: I2a72c072083b4492a223c6f9d73d21f4f424db62
+
+2020-10-30 03:46 +0000 [28faafd1c4]  Alexander Traud <pabstraud@compuserve.com>
+
+	* Compiler fixes for GCC when printf %s is NULL
+
+	  ASTERISK-29146
+
+	  Change-Id: Ib04bdad87d729f805f5fc620ef9952f58ea96d41
+
+2020-10-29 08:59 +0000 [914aecb8d8]  Alexander Traud <pabstraud@compuserve.com>
+
+	* Compiler fixes for GCC with -Os
+
+	  ASTERISK-29145
+
+	  Change-Id: I9af705f2b9725c53141aef5d0ff512a1800f073c
+
+2020-10-23 10:26 +0000 [cd32317691]  Alexander Traud <pabstraud@compuserve.com>
+
+	* chan_sip: On authentication, pick MD5 for sure.
+
+	  RFC 8760 added new digest-access-authentication schemes. Testing
+	  revealed that chan_sip does not pick MD5 if several schemes are offered
+	  by the User Agent Server (UAS). This change does not implement any of
+	  the new schemes like SHA-256. This change makes sure, MD5 is picked so
+	  UAS with SHA-2 enabled, like the service www.linphone.org/freesip, can
+	  still be used. This should have worked since day one because SIP/2.0
+	  already envisioned several schemes (see RFC 3261 and its augmented BNF
+	  for 'algorithm' which includes 'token' as third alternative; note: if
+	  'algorithm' was not present, MD5 is still assumed even in RFC 7616).
+
+	  Change-Id: I61ca0b1f74b5ec2b5f3062c2d661cafeaf597fcd
+
+2020-06-04 09:23 +0000 [1650d50e91]  Walter Doekes <walter+asterisk@wjd.nu>
+
+	* main/say: Work around gcc 9 format-truncation false positive
+
+	  Version: gcc (Ubuntu 9.3.0-10ubuntu2) 9.3.0
+	  Warning:
+	    say.c:2371:24: error: ‘%d’ directive output may be truncated writing
+	      between 1 and 11 bytes into a region of size 10
+	      [-Werror=format-truncation=]
+	    2371 |     snprintf(buf, 10, "%d", num);
+	    say.c:2371:23: note: directive argument in the range [-2147483648, 9]
+
+	  That's not possible though, as the if() starts out checking for (num < 0),
+	  making this Warning a false positive.
+
+	  (Also replaced some else<TAB>if with else<SP>if while in the vicinity.)
+
+	  Change-Id: Ic7a70120188c9aa525a6d70289385bfce878438a
+
+2020-10-19 15:31 +0000 [c62193c5de]  Kevin Harwell <kharwell@digium.com>
+
+	* res_pjsip, res_pjsip_session: initialize local variables
+
+	  This patch initializes a couple of local variables to some default values.
+	  Interestingly, in the 'pj_status_t dlg_status' case the value not being
+	  initialized caused memory to grow, and not be recovered, in the off nominal
+	  path (at least on my machine).
+
+	  Change-Id: I22ee65e1e1bff8efacea8a167c6c8428898523f7
+
+2020-10-23 09:55 +0000 [f3452c85e5]  Alexander Traud <pabstraud@compuserve.com>
+
+	* install_prereq: Add GMime 3.0.
+
+	  Ubuntu 20.10 does not come with GMime 2.6. Ubuntu 16.04 LTS does not
+	  come with GMime 3.0. aptitude ignores any missing package. Therefore,
+	  it installs the correct package(s). However, in Ubuntu 18.04 LTS and
+	  Ubuntu 20.04 LTS, both versions are installed alongside although only
+	  one is really needed.
+
+	  Change-Id: Ic58aa9f2e131d94671f286f17dbd61e1ccbabcb7
+
+2020-10-23 09:49 +0000 [db4320a6a0]  Alexander Traud <pabstraud@compuserve.com>
+
+	* BuildSystem: Enable Lua 5.4.
+
+	  Note to maintainers: Lua 5.4, Lua 5.3, and Lua 5.2 have not been tested
+	  at runtime with pbx_lua. Until then, use the lowest available version
+	  of Lua, if you enabled the module pbx_lua at all.
+
+	  Change-Id: Ie5270448b11fcb4e2a53d899e4fe7fea793ce7e0
+
+2020-10-13 12:15 +0000 [bd98e153d1]  Nick French <nickfrench@gmail.com>
+
+	* res_pjsip_session: Restore calls to ast_sip_message_apply_transport()
+
+	  Commit 44bb0858cb3ea6a8db8b8d1c7fedcfec341ddf66 ("debugging: Add enough
+	  to choke a mule") accidentally removed calls to
+	  ast_sip_message_apply_transport when it was attempting to just add
+	  debugging code.
+
+	  The kiss of death was saying that there were no functional changes in
+	  the commit comment.
+
+	  This makes outbound calls that use the 'flow' transport mechanism fail,
+	  since this call is used to relay headers into the outbound INVITE
+	  requests.
+
+	  ASTERISK-29124 #close
+
+	  Change-Id: I0f3e32c2e8ac415e30b1d29966d75a1546f0526a
+
+2020-10-22 11:21 +0000 [8f33e23dfb]  Sean Bright <sean.bright@gmail.com>
+
+	* features.conf.sample: Sample sound files incorrectly quoted
+
+	  ASTERISK-29136 #close
+
+	  Change-Id: I3186536d65a50014c8da4780c9224919caa81440
+
+2020-10-12 00:45 +0000 [0190e706b8]  Andrew Siplas <andrew@asiplas.net>
+
+	* logger.conf.sample: add missing comment mark
+
+	  Add missing comment mark from stock configuration.
+
+	  ASTERISK-29123 #close
+
+	  Change-Id: I4f94eb4544166bca8af4c17fd11edee3c6980620
+
+2020-10-06 10:32 +0000 [dcd2ed69a3]  Joshua C. Colp <jcolp@sangoma.com>
+
+	* res_pjsip: Adjust outgoing offer call pref.
+
+	  This changes the outgoing offer call preference
+	  default option to match the behavior of previous
+	  versions of Asterisk.
+
+	  The additional advanced codec negotiation options
+	  have also been removed from the sample configuration
+	  and marked as reserved for future functionality in
+	  XML documentation.
+
+	  The codec preference options have also been fixed to
+	  enforce local codec configuration.
+
+	  ASTERISK-29109
+
+	  Change-Id: Iad19347bd5f3d89900c15ecddfebf5e20950a1c2
+
+2020-09-30 15:00 +0000 [fa023cbfa0]  Sean Bright <sean.bright@gmail.com>
+
+	* tcptls.c: Don't close TCP client file descriptors more than once
+
+	  ASTERISK-28430 #close
+
+	  Change-Id: Ib556b0a0c95cca939e956886214ec8d828d89606
+
+2020-10-05 10:44 +0000 [61116d5dbc]  Jean Aunis <jean.aunis@prescom.fr>
+
+	* resource_endpoints.c: memory leak when providing a 404 response
+
+	  When handling a send_message request to a non-existing endpoint, the response's
+	  body is overriden and not properly freed.
+
+	  ASTERISK-29108
+
+	  Change-Id: Ie1d3d70065f80793445b60f5e4a7eb31b4b9c5c8
+
+2020-08-28 16:32 +0000 [56028426de]  Kevin Harwell <kharwell@digium.com>
+
+	* Logging: Add debug logging categories
+
+	  Added debug logging categories that allow a user to output debug
+	  information based on a specified category. This lets the user limit,
+	  and filter debug output to data relevant to a particular context,
+	  or topic. For instance the following categories are now available for
+	  debug logging purposes:
+
+	    dtls, dtls_packet, ice, rtcp, rtcp_packet, rtp, rtp_packet,
+	    stun, stun_packet
+
+	  These debug categories can be enable/disable via an Asterisk CLI command.
+
+	  While this overrides, and outputs debug data, core system debugging is
+	  not affected by this patch. Statements still output at their appropriate
+	  debug level. As well backwards compatibility has been maintained with
+	  past debug groups that could be enabled using the CLI (e.g. rtpdebug,
+	  stundebug, etc.).
+
+	  ASTERISK-29054 #close
+
+	  Change-Id: I6e6cb247bb1f01dbf34750b2cd98e5b5b41a1849
+
+2020-09-29 13:04 +0000 [51cba591e3]  Sean Bright <sean.bright@gmail.com>
+
+	* pbx.c: On error, ast_add_extension2_lockopt should always free 'data'
+
+	  In the event that the desired extension already exists,
+	  ast_add_extension2_lockopt() will free the 'data' it is passed before
+	  returning an error, so we should not be freeing it ourselves.
+
+	  Additionally, there were two places where ast_add_extension2_lockopt()
+	  could return an error without also freeing the 'data' pointer, so we
+	  add that.
+
+	  ASTERISK-29097 #close
+
+	  Change-Id: I904707aae55169feda050a5ed7c6793b53fe6eae
+
+2020-09-24 13:46 +0000 [773f424c7f]  George Joseph <gjoseph@digium.com>
+
+	* app_confbridge/bridge_softmix:  Add ability to force estimated bitrate
+
+	  app_confbridge now has the ability to set the estimated bitrate on an
+	  SFU bridge.  To use it, set a bridge profile's remb_behavior to "force"
+	  and set remb_estimated_bitrate to a rate in bits per second.  The
+	  remb_estimated_bitrate parameter is ignored if remb_behavior is something
+	  other than "force".
+
+	  Change-Id: Idce6464ff014a37ea3b82944452e56cc4d75ab0a
+
+2020-09-29 19:57 +0000 [4b5ed817bd]  Sean Bright <sean.bright@gmail.com>
+
+	* app_voicemail.c: Document VMSayName interruption behavior
+
+	  ASTERISK-26424 #close
 
-	  Change-Id: I17da008540ee2e8496b644d05f995b320b54ad7a
+	  Change-Id: I797ad0ed302d0b3d2c90543eff5b7207ed08ecf0
 
-2020-10-12 05:30 +0000 [fd57fae048]  Joshua C. Colp <jcolp@sangoma.com>
+2020-09-22 22:39 +0000 [9c0ded6e76]  Holger Hans Peter Freyther <holger@moiji-mobile.com>
 
-	* voicemail: add option 'e' to play greetings as early media
+	* res_pjsip_sdp_rtp: Fix accidentally native bridging calls
 
-	  When using this option, answering the channel is deferred until
-	  all prompts/greetings have been played and the caller is about
-	  to leave their message.
+	  Stop advertising RFC2833 support on the rtp_engine when DTMF mode is
+	  auto but no tel_event was found inside SDP file.
 
-	  ASTERISK-29118 #close
+	  On an incoming call create_rtp will be called and when session->dtmf is
+	  set to AST_SIP_DTMF_AUTO, the AST_RTP_PROPERTY_DTMF will be set without
+	  looking at the SDP file.
 
-	  Change-Id: I41b9f0428783c0bd697c8c994f906d1e75ce9ddb
+	  Once get_codecs gets called we move the DTMF mode from RFC2833 to INBAND
+	  but continued to advertise RFC2833 support.
 
-2020-11-02 01:24 +0000 [bf9f0f13c4]  Alexander Traud <pabstraud@compuserve.com>
+	  This meant the native_rtp bridge would falsely consider the two channels
+	  as compatible. In addition to changing the DTMF mode we now set or
+	  remove the AST_RTP_PROPERTY_DTMF.
 
-	* loader: Sync load- and build-time deps.
+	  The property is checked in ast_rtp_dtmf_compatible and called by
+	  native_rtp_bridge_compatible.
 
-	  In MODULEINFO, each depend has to be listed in .requires of AST_MODULE_INFO.
+	  ASTERISK-29051 #close
 
-	  ASTERISK-29148
+	  Change-Id: I1e0c1e324598a437932c0b7836bcb626aba8e287
 
-	  Change-Id: I254dd33194ae38d2877b8021c57c2a5deb6bbcd2
+2020-09-28 07:42 +0000 [990c72bbcf]  laszlovl <digium@lvlconsultancy.nl>
 
-2020-11-18 13:11 +0000 [994fbdaf48]  Sean Bright <sean.bright@gmail.com>
+	* res_musiconhold: Load all realtime entries, not just the first
 
-	* CHANGES: Remove already applied CHANGES update
+	  ASTERISK-29099
 
-	  Change-Id: Iee7163bc732d58c5cbaa2cfab1f5aab4a412060a
+	  Change-Id: I45636679c0fb5a5f59114c8741626631a604e8a6
 
-2020-11-17 14:19 +0000 [c79bd583d9]  Alexander Greiner-Baer <alex+asterisk@greiner-baer.de>
+2020-09-23 04:05 +0000 [e831952eba]  Jasper van der Neut <jasper@isotopic.nl>
 
-	* res_pjsip: set Accept-Encoding to identity in OPTIONS response
+	* channels: Don't dereference NULL pointer
 
+	  Check result of ast_translator_build_path against NULL before dereferencing.
 
+	  ASTERISK-29091
 
-	  RFC 3261 says that the Accept-Encoding header should be present
-	  in an options response. Permitted values according to RFC 2616
-	  are only compression algorithms like gzip or the default identity
-	  encoding. Therefore "text/plain" is not a correct value here.
-	  As long as the header is hard coded, it should be set to "identity".
+	  Change-Id: Ia3538ea190bd371f70c9dd49984b021765691b29
 
-	  Without this fix an Alcatel OmniPCX periodically logs warnings like
-	  "[sip_acceptIncorrectHeader] Header Accept-Encoding is malformed"
-	  on a SIP Trunk.
+2020-09-24 09:54 +0000 [e7bd97e2e5]  Torrey Searle <tsearle@voxbone.com>
 
-	  ASTERISK-29165 #close
+	* res_pjsip_diversion: fix double 181
 
-	  Change-Id: I0aa2211ebf0b4c2ed554ac7cda794523803a3840
+	  Arming response to both AST_SIP_SESSION_BEFORE_REDIRECTING and
+	  AST_SIP_SESSION_BEFORE_MEDIA causes 302 to to be handled twice,
+	  resulting in to 181 being generated.
 
-2020-11-04 07:39 +0000 [e884d935f6]  Alexander Traud <pabstraud@compuserve.com>
+	  Change-Id: I866e5461564644ffb8a5e12b6f1330b50a7b63ab
 
-	* chan_sip: Remove unused sip_socket->port.
+2020-09-24 11:47 +0000 [505211551a]  Sean Bright <sean.bright@gmail.com>
 
-	  12 years ago, with ASTERISK_12115 the last four get/uses of socket.port
-	  vanished. However, the struct member itself and all seven set/uses
-	  remained as dead code.
+	* res_musiconhold: Clarify that playlist mode only supports HTTP(S) URLs
 
-	  ASTERISK-28798
+	  Change-Id: I41e77a04e4a523f4ed61a7a20b738ffd42be441e
 
-	  Change-Id: Ib90516a49eca3d724a70191278aaf2144fb58c59
+2020-09-23 15:20 +0000 [16dfe8f03f]  Sean Bright <sean.bright@gmail.com>
 
-2020-11-13 06:19 +0000 [33e3542132]  Boris P. Korzun <drtr0jan@yandex.ru>
+	* dsp.c: Update calls to ast_format_cmp to check result properly
 
-	* bridge_basic: Fixed setup of recall channels
+	  ASTERISK-28311 #close
 
-	  Fixed a bug (like a typo) in retransfer_enter()
-	  at main/bridge_basic.c:2641. common_recall_channel_setup() setups
-	  common things on the recalled transfer target, but used same target
-	  as source instead trasfered.
+	  Change-Id: Ib1ce8fc1a8752751f5bf3615c59245532dfd9aa2
 
-	  ASTERISK-29161 #close
+2020-09-22 05:05 +0000 [23e427bbd2]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  Change-Id: Ieb549654a621c38b1ad5e9d15b9f18823d9cc31f
+	* res_pjsip_session: Fix stream name memory leak.
 
-2020-11-03 02:27 +0000 [6e1fb58183]  Alexander Traud <pabstraud@compuserve.com>
+	  When constructing a stream name based on the media type
+	  and position the allocated name was not being freed
+	  causing a leak.
 
-	* modules.conf: Align the comments for more conclusiveness.
+	  Change-Id: I52510863b24a2f531f0a55b440bb2c81844029de
 
-	  Change-Id: I79cc693cd5a6e5dd7d403b7e91d970ff1ddf8306
+2020-09-18 08:09 +0000 [b11b49945b]  Sean Bright <sean.bright@gmail.com>
 
-2020-11-11 08:55 +0000 [2413598705]  George Joseph <gjoseph@digium.com>
+	* func_curl.c: Prevent crash when using CURLOPT(httpheader)
 
-	* app_queue: Fix deadlock between update and show queues
+	  Because we use shared thread-local cURL instances, we need to ensure
+	  that the state of the cURL instance is correct before each invocation.
 
-	  Operations that update queues when shared_lastcall is set lock the
-	  queue in question, then have to lock the queues container to find the
-	  other queues with the same member. On the other hand, __queues_show
-	  (which is called by both the CLI and AMI) does the reverse. It locks
-	  the queues container, then iterates over the queues locking each in
-	  turn to display them.  This creates a deadlock.
+	  In the case of custom headers, we were not resetting cURL's internal
+	  HTTP header pointer which could result in a crash if subsequent
+	  requests do not configure custom headers.
 
-	  * Moved queue print logic from __queues_show to a separate function
-	    that can be called for a single queue.
+	  ASTERISK-29085 #close
 
-	  * Updated __queues_show so it doesn't need to lock or traverse
-	    the queues container to show a single queue.
+	  Change-Id: I8b4ab34038156dfba613030a45f10e932d2e992d
 
-	  * Updated __queues_show to snap a copy of the queues container and iterate
-	    over that instead of locking the queues container and iterating over
-	    it while locked.  This prevents us from having to hold both the
-	    container lock and the queue locks at the same time.  This also
-	    allows us to sort the queue entries.
+2020-09-18 15:02 +0000 [0aaf9aa6de]  Sean Bright <sean.bright@gmail.com>
 
-	  ASTERISK-29155
+	* res_musiconhold: Start playlist after initial announcement
 
-	  Change-Id: I78d4dc36728c2d7bc187b97d82673fc77f2bcf41
+	  Only track our sample offset if we are playing a non-announcement file,
+	  otherwise we will skip that number of samples when we start playing the
+	  first MoH file.
 
-2020-11-12 12:36 +0000  Asterisk Development Team <asteriskteam@digium.com>
+	  ASTERISK-24329 #close
 
-	* asterisk 18.1.0-rc1 Released.
+	  Change-Id: Ib6b3c84fcaa1063889ab38ba7e7fc50050a3ccfc
 
-2020-11-12 05:50 +0000 [98d1537c1e]  Asterisk Development Team <asteriskteam@digium.com>
+2020-09-22 05:13 +0000 [f67f5676b7]  Joshua C. Colp <jcolp@sangoma.com>
 
-	* Update CHANGES and UPGRADE.txt for 18.1.0
-2020-11-02 13:53 +0000 [860e40dd80]  George Joseph <gjoseph@digium.com>
+	* res_pjsip_session: Fix session reference leak.
 
-	* res_pjsip_outbound_registration.c:  Use our own scheduler and other stuff
+	  The ast_sip_dialog_get_session function returns the session
+	  with reference count increased. This was not taken into
+	  account and was causing sessions to remain around when they
+	  should not be.
 
-	  * Instead of using the pjproject timer heap, we now use our own
-	    pjsip_scheduler.  This allows us to more easily debug and allows us to
-	    see times in "pjsip show/list registrations" as well as being able to
-	    see the registrations in "pjsip show scheduled_tasks".
+	  ASTERISK-29089
 
-	  * Added the last registration time, registration interval, and the next
-	    registration time to the CLI output.
+	  Change-Id: I430fa721b0a824311a59effec6056e9ec528e3e8
 
-	  * Removed calls to pjsip_regc_info() except where absolutely necessary.
-	    Most of the calls were just to get the server and client URIs for log
-	    messages so we now just save them on the client_state object when we
-	    create it.
+2020-09-16 08:01 +0000 [b4ab0dd41a]  Michal Hajek <michal.hajek@daktela.com>
 
-	  * Added log messages where needed and updated most of the existong ones
-	    to include the registration object name at the start of the message.
+	* res_stasis.c: Add compare function for bridges moh container
 
-	  Change-Id: I4534a0fc78c7cb69f23b7b449dda9748c90daca2
+	  Sometimes not play MOH on bridge.
 
-2020-11-02 13:53 +0000 [569fc28966]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-29081
+	  Reported-by: Michal Hajek <michal.hajek@daktela.com>
 
-	* pjsip_scheduler.c: Add type ONESHOT and enhance cli show command
+	  Change-Id: I760c73e0c9be1d340303b5d1c18a00c4759e8232
 
-	  * Added a ONESHOT type that never reschedules.
+2020-09-17 11:40 +0000 [923d95cc84]  George Joseph <gjoseph@digium.com>
 
-	  * Added "like" capability to "pjsip show scheduled_tasks" so you can do
-	    the following:
+	* logger.h: Fix ast_trace to respect scope_level
 
-	    CLI> pjsip show scheduled_tasks like outreg
-	    PJSIP Scheduled Tasks:
+	  ast_trace() was always emitting messages when it's level was set to -1
+	  because it was ignoring scope_level.
 
-	    Task Name                                     Interval  Times Run ...
-	    ============================================= ========= ========= ...
-	    pjsip/outreg/testtrunk-reg-0-00000074            50.000   oneshot ...
-	    pjsip/outreg/voipms-reg-0-00000073              110.000   oneshot ...
+	  Change-Id: I849c8f4f4613899c37f82be0202024e7d117e506
 
-	  * Fixed incorrect display of "Next Start".
+2020-09-15 10:48 +0000 [52ca2323aa]  Sean Bright <sean.bright@gmail.com>
 
-	  * Compacted the displays of times in the CLI.
+	* chan_sip.c: Don't build by default
 
-	  * Added two new functions (ast_sip_sched_task_get_times2,
-	    ast_sip_sched_task_get_times_by_name2) that retrieve the interval,
-	    next start time, and next run time in addition to the times already
-	    returned by ast_sip_sched_task_get_times().
+	  ASTERISK-29083 #close
 
-	  Change-Id: Ie718ca9fd30490b8a167bedf6b0b06d619dc52f3
+	  Change-Id: I9ea25fba3ba8f63a47886894bd966e0f08d5e003
 
-2020-10-02 14:32 +0000 [da0f2ea99e]  Alexei Gradinari <alex2grad@gmail.com>
+2020-09-15 15:44 +0000 [5a0e1d256d]  Sean Bright <sean.bright@gmail.com>
 
-	* sched: AST_SCHED_REPLACE_UNREF can lead to use after free of data
+	* audiosocket: Fix module menuselect descriptions
 
-	  The data can be freed if the old object '_data' is the same object as
-	  new 'data'. Because at first the object is unreferenced which can lead
-	  to destroying it.
+	  The module description needs to be on the same line as the
+	  AST_MODULE_INFO or it is not parsed correctly.
 
-	  This could happened in res_pjsip_pubsub when the publication is updated
-	  which could lead to segfault in function publish_expire.
+	  Change-Id: I9ba11df1415369790e8656fcb527bb2749373c21
 
-	  Change-Id: I0164f57c387243510bdbd2f8dcf33377b6c202da
+2020-09-17 13:01 +0000 [39bb45cdfc]  George Joseph <gjoseph@digium.com>
 
-2020-10-30 11:43 +0000 [5a6037778b]  Alexander Traud <pabstraud@compuserve.com>
+	* bridge_softmix/sfu_topologies_on_join: Ignore topology change failures
 
-	* res_pjsip/config_transport: Load and run without OpenSSL.
+	  When a channel joins a bridge, we do topology change requests on all
+	  existing channels to add the new participant to them.  However the
+	  announcer channel will return an error because it doesn't support
+	  topology in the first place.  Unfortunately, there doesn't seem to be a
+	  reliable way to tell if the error is expected or not so the error is
+	  ignored for all channels.  If the request fails on a "real" channel,
+	  that channel just won't get the new participant's video.
 
-	  ASTERISK-28933
-	  Reported-by: Walter Doekes
+	  Change-Id: Ic95db4683f27d224c1869fe887795d6b9fdea4f0
 
-	  Change-Id: I65eac49e5b0a79261ea80e2b9b38a836886ed59f
+2020-09-15 16:16 +0000 [bc038e6191]  Sean Bright <sean.bright@gmail.com>
 
-2020-10-30 05:53 +0000 [be54c7e9ea]  Alexander Traud <pabstraud@compuserve.com>
+	* res_pjsip_session.c: Fix build when TEST_FRAMEWORK is not defined
 
-	* res_stir_shaken: Include OpenSSL headers where used actually.
+	  Change-Id: Id4852c26e9c412af8e37b5dd3c15da9453ad3276
 
-	  This avoids the inclusion of the OpenSSL headers in the public header,
-	  which avoids one external library dependency in res_pjsip_stir_shaken.
+2020-08-13 03:34 +0000 [888090ab18]  Torrey Searle <tsearle@voxbone.com>
 
-	  Change-Id: I6a07e2d81d2b5442e24e99b8cc733a99f881dcf4
+	* res_pjsip_diversion: implement support for History-Info
 
-2020-10-18 13:40 +0000 [c635c78265]  Dovid Bender <dovid@telecurve.com>
+	  Implemention of History-Info capable of interworking with Diversion
+	  Header following RFC7544
 
-	* func_curl.c: Allow user to set what return codes constitute a failure.
+	  ASTERISK-29027 #close
 
-	  Currently any response from res_curl where we get an answer from the
-	  web server, regardless of what the response is (404, 403 etc.) Asterisk
-	  currently treats it as a success. This patch allows you to set which
-	  codes should be considered as a failure by Asterisk. If say we set
-	  failurecodes=404,403 then when using curl in realtime if a server gives
-	  a 404 error Asterisk will try to failover to the next option set in
-	  extconfig.conf
+	  Change-Id: I2296369582d4b295c5ea1e60bec391dd1d318fa6
 
-	  ASTERISK-28825
+2020-09-14 13:23 +0000 [30e08ce1bb]  Sean Bright <sean.bright@gmail.com>
 
-	  Reported by: Dovid Bender
-	  Code by: Gobinda Paul
+	* format_cap: Perform codec lookups by pointer instead of name
+
+	  ASTERISK-28416 #close
+
+	  Change-Id: I069420875ebdbcaada52d92599a5f7de3cb2cdf4
+
+2020-09-11 11:09 +0000 [53910b1f25]  George Joseph <gjoseph@digium.com>
+
+	* res_pjsip_session: Fix issue with COLP and 491
+
+	  The recent 491 changes introduced a check to determine if the active
+	  and pending topologies were equal and to suppress the re-invite if they
+	  were. When a re-invite is sent for a COLP-only change, the pending
+	  topology is NULL so that check doesn't happen and the re-invite is
+	  correctly sent. Of course, sending the re-invite sets the pending
+	  topology.  If a 491 is received, when we resend the re-invite, the
+	  pending topology is set and since we didn't request a change to the
+	  topology in the first place, pending and active topologies are equal so
+	  the topologies-equal check causes the re-invite to be erroneously
+	  suppressed.
+
+	  This change checks if the topologies are equal before we run the media
+	  state resolver (which recreates the pending topology) so that when we
+	  do the final topologies-equal check we know if this was a topology
+	  change request.  If it wasn't a change request, we don't suppress
+	  the re-invite even though the topologies are equal.
+
+	  ASTERISK-29014
+
+	  Change-Id: Iffd7dd0500301156a566119ebde528d1a9573314
+
+2020-08-20 15:09 +0000 [44bb0858cb]  George Joseph <gjoseph@digium.com>
+
+	* debugging:  Add enough to choke a mule
+
+	  Added to:
+	   * bridges/bridge_softmix.c
+	   * channels/chan_pjsip.c
+	   * include/asterisk/res_pjsip_session.h
+	   * main/channel.c
+	   * res/res_pjsip_session.c
+
+	  There NO functional changes in this commit.
+
+	  Change-Id: I06af034d1ff3ea1feb56596fd7bd6d7939dfdcc3
+
+2020-08-20 11:21 +0000 [86f1bce186]  George Joseph <gjoseph@digium.com>
+
+	* res_pjsip_session:  Handle multi-stream re-invites better
+
+	  When both Asterisk and a UA send re-invites at the same time, both
+	  send 491 "Transaction in progress" responses to each other and back
+	  off a specified amount of time before retrying. When Asterisk
+	  prepares to send its re-invite, it sets up the session's pending
+	  media state with the new topology it wants, then sends the
+	  re-invite.  Unfortunately, when it received the re-invite from the
+	  UA, it partially processed the media in the re-invite and reset
+	  the pending media state before sending the 491 losing the state it
+	  set in its own re-invite.
 
-	  Change-Id: I94443e508343e0a3e535e51ea6e0562767639987
+	  Asterisk also was not tracking re-invites received while an existing
+	  re-invite was queued resulting in sending stale SDP with missing
+	  or duplicated streams, or no re-invite at all because we erroneously
+	  determined that a re-invite wasn't needed.
 
-2020-11-04 15:08 +0000 [6baa4b53be]  Kevin Harwell <kharwell@sangoma.com>
+	  There was also an issue in bridge_softmix where we were using a stream
+	  from the wrong topology to determine if a stream was added.  This also
+	  caused us to erroneously determine that a re-invite wasn't needed.
 
-	* AST-2020-001 - res_pjsip: Return dialog locked and referenced
+	  Regardless of how the delayed re-invite was triggered, we need to
+	  reconcile the topology that was active at the time the delayed
+	  request was queued, the pending topology of the queued request,
+	  and the topology currently active on the session.  To do this we
+	  need a topology resolver AND we need to make stream named unique
+	  so we can accurately tell what a stream has been added or removed
+	  and if we can re-use a slot in the topology.
 
-	  pjproject returns the dialog locked and with a reference. However,
-	  in Asterisk the method that handles this decrements the reference
-	  and removes the lock prior to returning. This makes it possible,
-	  under some circumstances, for another thread to free said dialog
-	  before the thread that created it attempts to use it again. Of
-	  course when the thread that created it tries to use a freed dialog
-	  a crash can occur.
+	  Summary of changes:
 
-	  This patch makes it so Asterisk now returns the newly created
-	  dialog both locked, and with an added reference. This allows the
-	  caller to de-reference, and unlock the dialog when it is safe to
-	  do so.
+	   * bridge_softmix:
+	     * We no longer reset the stream name to "removed" in
+	       remove_all_original_streams().  That was causing  multiple streams
+	       to have the same name and wrecked the checks for duplicate streams.
 
-	  In the case of a new SIP Invite the lock, and reference are now
-	  held for the entirety of the new invite handling process.
-	  Otherwise it's possible for the dialog, or its dependent objects,
-	  like the transaction, to disappear. For example if there is a TCP
-	  transport error.
+	     * softmix_bridge_stream_sources_update() was checking the old_stream
+	       to see if it had the softmix prefix and not considering the stream
+	       as "new" if it did.  If the stream in that slot has something in it
+	       because another re-invite happened, then that slot in old might
+	       have a softmix stream but the same stream in new might actually
+	       be a new one.  Now we check the new_stream's name instead of
+	       the old_stream's.
 
-	  ASTERISK-29057 #close
+	   * stream:
+	     * Instead of using plain media type name ("audio", "video", etc) as
+	       the default stream name, we now append the stream position to it
+	       to make it unique.  We need to do this so we can distinguish multiple
+	       streams of the same type from each other.
 
-	  Change-Id: I5ef645a47829596f402cf383dc02c629c618969e
+	     * When we set a stream's state to REMOVED, we no longer reset its
+	       name to "removed" or destroy its metadata.  Again, we need to
+	       do this so we can distinguish multiple streams of the same
+	       type from each other.
 
-2020-11-03 10:38 +0000 [82325ba58b]  Ben Ford <bford@digium.com>
+	   * res_pjsip_session:
+	     * Added resolve_refresh_media_states() that takes in 3 media states
+	       and creates an up-to-date pending media state that includes the changes
+	       that might have happened while a delayed session refresh was in the
+	       delayed queue.
 
-	* AST-2020-002 - res_pjsip: Stop sending INVITEs after challenge limit.
+	     * Added is_media_state_valid() that checks the consistency of
+	       a media state and returns a true/false value. A valid state has:
+	       * The same number of stream entries as media session entries.
+	           Some media session entries can be NULL however.
+	       * No duplicate streams.
+	       * A valid stream for each non-NULL media session.
+	       * A stream that matches each media session's stream_num
+	         and media type.
 
-	  If Asterisk sends out and INVITE and receives a challenge with a
-	  different nonce value each time, it will continually send out INVITEs,
-	  even if the call is hung up. The endpoint must be configured for
-	  outbound authentication in order for this to occur. A limit has been set
-	  on outbound INVITEs so that, once reached, Asterisk will stop sending
-	  INVITEs and the transaction will terminate.
+	     * Updated handle_incoming_sdp() to set the stream name to include the
+	       stream position number in the name to make it unique.
 
-	  ASTERISK-29013
+	     * Updated the ast_sip_session_delayed_request structure to include both
+	       the pending and active media states and updated the associated delay
+	       functions to process them.
 
-	  Change-Id: I2d001ca745b00ca8aa12030f2240cd72363b46f7
+	     * Updated sip_session_refresh() to accept both the pending and active
+	       media states that were in effect when the request was originally queued
+	       and to pass them on should the request need to be delayed again.
 
-2020-10-29 10:21 +0000 [fe540d0326]  Sean Bright <sean.bright@gmail.com>
+	     * Updated sip_session_refresh() to call resolve_refresh_media_states()
+	       and substitute its results for the pending state passed in.
 
-	* sip_to_pjsip.py: Handle #include globs and other fixes
+	     * Updated sip_session_refresh() with additional debugging.
 
-	  * Wildcards in #includes are now properly expanded
+	     * Updated session_reinvite_on_rx_request() to simply return PJ_FALSE
+	       to pjproject if a transaction is in progress.  This stops us from
+	       creating a partial pending media state that would be invalid later on.
 
-	  * Implement operators for Section class to allow sorting
+	     * Updated reschedule_reinvite() to clone both the current pending and
+	       active media states and pass them to delay_request() so the resolver
+	       can tell what the original intention of the re-invite was.
 
-	  ASTERISK-29142 #close
+	     * Added a large unit test for the resolver.
 
-	  Change-Id: I9b9cd95f4cbe5c24506b75d17173c5aa1a83e5df
+	  ASTERISK-29014
 
-2020-10-29 03:55 +0000 [e0ee53dc9c]  Alexander Traud <pabstraud@compuserve.com>
+	  Change-Id: Id3440972943c611a15f652c6c569fa0e4536bfcb
 
-	* Compiler fixes for GCC with -Og
+2020-08-31 07:21 +0000 [9052e448ec]  Sungtae Kim <pchero21@gmail.com>
 
-	  ASTERISK-29144
+	* realtime: Increased reg_server character size
 
-	  Change-Id: I2a72c072083b4492a223c6f9d73d21f4f424db62
+	  Currently, the ps_contacts table's reg_server column in realtime database type is varchar(20).
+	  This is fine for normal cases, but if the hostname is longer than 20, it returns error and then
+	  failed to register the contact address of the peer.
 
-2020-10-29 08:59 +0000 [2dacadd9df]  Alexander Traud <pabstraud@compuserve.com>
+	  Normally, 20 characters limitation for the hostname is fine, but with the cloud env.
+	  So, increased the size to 255.
 
-	* Compiler fixes for GCC with -Os
+	  ASTERISK-29056
 
-	  ASTERISK-29145
+	  Change-Id: Iac52c8c35030303cfa551bb39f410b33bffc507d
 
-	  Change-Id: I9af705f2b9725c53141aef5d0ff512a1800f073c
+2020-08-30 15:42 +0000 [aae0904c7d]  Sungtae Kim <pchero21@gmail.com>
 
-2020-10-30 03:46 +0000 [f86af1fbd0]  Alexander Traud <pabstraud@compuserve.com>
+	* res_stasis.c: Added video_single option for bridge creation
 
-	* Compiler fixes for GCC when printf %s is NULL
+	  Currently, it was not possible to create bridge with video_mode single.
+	  This made hard to put the bridge in a vidoe_single mode.
+	  So, added video_single option for Bridge creation using the ARI.
+	  This allows create a bridge with video_mode single.
 
-	  ASTERISK-29146
+	  ASTERISK-29055
 
-	  Change-Id: Ib04bdad87d729f805f5fc620ef9952f58ea96d41
+	  Change-Id: I43e720e5c83fc75fafe10fe22808ae7f055da2ae
 
-2020-10-23 10:26 +0000 [5b25c75d7b]  Alexander Traud <pabstraud@compuserve.com>
+2020-08-31 11:14 +0000 [80a609fcce]  Ben Ford <bford@digium.com>
 
-	* chan_sip: On authentication, pick MD5 for sure.
+	* Bridging: Use a ref to bridge_channel's channel to prevent crash.
 
-	  RFC 8760 added new digest-access-authentication schemes. Testing
-	  revealed that chan_sip does not pick MD5 if several schemes are offered
-	  by the User Agent Server (UAS). This change does not implement any of
-	  the new schemes like SHA-256. This change makes sure, MD5 is picked so
-	  UAS with SHA-2 enabled, like the service www.linphone.org/freesip, can
-	  still be used. This should have worked since day one because SIP/2.0
-	  already envisioned several schemes (see RFC 3261 and its augmented BNF
-	  for 'algorithm' which includes 'token' as third alternative; note: if
-	  'algorithm' was not present, MD5 is still assumed even in RFC 7616).
+	  There's a race condition with bridging where a bridge can be torn down
+	  causing the bridge_channel's ast_channel to become NULL when it's still
+	  needed. This particular case happened with attended transfers, but the
+	  crash occurred when trying to publish a stasis message. Now, the
+	  bridge_channel is locked, a ref to the ast_channel is obtained, and that
+	  ref is passed down the chain.
 
-	  Change-Id: I61ca0b1f74b5ec2b5f3062c2d661cafeaf597fcd
+	  Change-Id: Ic48715c0c041615d17d286790ae3e8c61bb28814
 
-2020-06-04 09:23 +0000 [fb3b14ab7d]  Walter Doekes <walter+asterisk@wjd.nu>
+2020-09-01 08:43 +0000 [f8fe20eb9f]  Patrick Verzele <patrick@verzele.be>
 
-	* main/say: Work around gcc 9 format-truncation false positive
+	* res_pjsip_session: Deferred re-INVITE without SDP send a=sendrecv instead of a=sendonly
 
-	  Version: gcc (Ubuntu 9.3.0-10ubuntu2) 9.3.0
-	  Warning:
-	    say.c:2371:24: error: ‘%d’ directive output may be truncated writing
-	      between 1 and 11 bytes into a region of size 10
-	      [-Werror=format-truncation=]
-	    2371 |     snprintf(buf, 10, "%d", num);
-	    say.c:2371:23: note: directive argument in the range [-2147483648, 9]
+	  Building on ASTERISK-25854. When the device requests hold by sending SDP with attribute recvonly, asterisk places the session in sendonly mode. When the device later requests to resume the call by using a re-INVITE excluding SDP, asterisk needs to change the sendonly mode to sendrecv again.
 
-	  That's not possible though, as the if() starts out checking for (num < 0),
-	  making this Warning a false positive.
+	  Change-Id: I60341ce3d87f95869f3bc6dc358bd3e8286477a6
 
-	  (Also replaced some else<TAB>if with else<SP>if while in the vicinity.)
+2020-08-28 16:31 +0000 [1a5597741f]  Kevin Harwell <kharwell@digium.com>
 
-	  Change-Id: Ic7a70120188c9aa525a6d70289385bfce878438a
+	* conversions: Add string to signed integer conversion functions
 
-2020-10-19 15:31 +0000 [439f7bb848]  Kevin Harwell <kharwell@digium.com>
+	  Change-Id: Id603b0b03b78eb84c7fca030a08b343c0d5973f9
 
-	* res_pjsip, res_pjsip_session: initialize local variables
+2020-08-26 04:58 +0000 [c3a3ab8628]  Kfir Itzhak <mastertheknife@gmail.com>
 
-	  This patch initializes a couple of local variables to some default values.
-	  Interestingly, in the 'pj_status_t dlg_status' case the value not being
-	  initialized caused memory to grow, and not be recovered, in the off nominal
-	  path (at least on my machine).
+	* app_queue: Fix leave-empty not recording a call as abandoned
 
-	  Change-Id: I22ee65e1e1bff8efacea8a167c6c8428898523f7
+	  This fixes a bug introduced mistakenly in ASTERISK-25665:
+	  If leave-empty is enabled, a call may sometimes be removed from
+	  a queue without recording it as abandoned.
+	  This causes Asterisk to not generate an abandon event for that
+	  call, and for the queue abandoned counter to be incorrect.
 
-2020-10-23 09:55 +0000 [f89531cb98]  Alexander Traud <pabstraud@compuserve.com>
+	  ASTERISK-29043 #close
 
-	* install_prereq: Add GMime 3.0.
+	  Change-Id: I1a71b81df78adff59af587f1d8483cf57df430c7
 
-	  Ubuntu 20.10 does not come with GMime 2.6. Ubuntu 16.04 LTS does not
-	  come with GMime 3.0. aptitude ignores any missing package. Therefore,
-	  it installs the correct package(s). However, in Ubuntu 18.04 LTS and
-	  Ubuntu 20.04 LTS, both versions are installed alongside although only
-	  one is really needed.
+2020-08-28 09:34 +0000 [5989e0de0f]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: Ic58aa9f2e131d94671f286f17dbd61e1ccbabcb7
+	* ast_coredumper: Fix issues with naming
 
-2020-10-13 12:15 +0000 [f041763e3b]  Nick French <nickfrench@gmail.com>
+	  If you run ast_coredumper --tarball-coredumps in the same directory
+	  as the actual coredump, tar can fail because the link to the
+	  actual coredump becomes recursive.  The resulting tarball will
+	  have everything _except_ the coredump (which is usually what
+	  you need)
 
-	* res_pjsip_session: Restore calls to ast_sip_message_apply_transport()
+	  There's also an issue that the directory name in the tarball
+	  is the same as the coredump so if you extract the tarball the
+	  directory it creates will overwrite the coredump.
 
-	  Commit 44bb0858cb3ea6a8db8b8d1c7fedcfec341ddf66 ("debugging: Add enough
-	  to choke a mule") accidentally removed calls to
-	  ast_sip_message_apply_transport when it was attempting to just add
-	  debugging code.
+	  So:
 
-	  The kiss of death was saying that there were no functional changes in
-	  the commit comment.
+	   * Made the link to the coredump use the absolute path to the
+	     file instead of a relative one.  This prevents the recursive
+	     link and allows tar to add the coredump.
 
-	  This makes outbound calls that use the 'flow' transport mechanism fail,
-	  since this call is used to relay headers into the outbound INVITE
-	  requests.
+	   * The tarballed directory is now named <coredump>.output instead
+	     of just <coredump> so if you expand the tarball it won't
+	     overwrite the coredump.
 
-	  ASTERISK-29124 #close
+	  Change-Id: I8b3eeb26e09a577c702ff966924bb0a2f9a759ea
 
-	  Change-Id: I0f3e32c2e8ac415e30b1d29966d75a1546f0526a
+2020-08-28 04:29 +0000 [c4bed96742]  Joshua C. Colp <jcolp@sangoma.com>
 
-2020-10-23 09:49 +0000 [2773f93154]  Alexander Traud <pabstraud@compuserve.com>
+	* parking: Copy parker UUID as well.
 
-	* BuildSystem: Enable Lua 5.4.
+	  When fixing issues uncovered by GCC10 a copy of the parker UUID
+	  was removed accidentally. This change restores it so that the
+	  subscription has the data it needs.
 
-	  Note to maintainers: Lua 5.4, Lua 5.3, and Lua 5.2 have not been tested
-	  at runtime with pbx_lua. Until then, use the lowest available version
-	  of Lua, if you enabled the module pbx_lua at all.
+	  ASTERISK-29042
 
-	  Change-Id: Ie5270448b11fcb4e2a53d899e4fe7fea793ce7e0
+	  Change-Id: I7d396a14ea648bd26d3c363dd78e78bd386b544a
 
-2020-10-22 11:21 +0000 [6f321b561a]  Sean Bright <sean.bright@gmail.com>
+2020-08-26 10:43 +0000 [f225e9bf35]  Alexander Traud <pabstraud@compuserve.com>
 
-	* features.conf.sample: Sample sound files incorrectly quoted
+	* sip_nat_settings: Update script for latest Linux.
 
-	  ASTERISK-29136 #close
+	  With the latest Linux, 'ifconfig' is not installed on default anymore.
+	  Furthermore, the output of the current net-tools 'ifconfig' changed.
+	  Therefore, parsing failed. This update uses 'ip addr show' instead.
+	  Finally, the service for the external IP changed.
 
-	  Change-Id: I3186536d65a50014c8da4780c9224919caa81440
+	  Change-Id: I9b1a7c3f457e3553b50a3e9a55524e40d70245a0
 
-2020-10-12 00:45 +0000 [ff33f7f44f]  Andrew Siplas <andrew@asiplas.net>
+2020-08-26 10:19 +0000 [8907a9f0b9]  Alexander Traud <pabstraud@compuserve.com>
 
-	* logger.conf.sample: add missing comment mark
+	* samples: Fix keep_alive_interval default in pjsip.conf.
 
-	  Add missing comment mark from stock configuration.
+	  Since ASTERISK_27978 the default is not off but 90 seconds. That change
+	  happened because ASTERISK_27347 disabled the keep-alives in the bundled
+	  PJProject and Asterisk should behave the same as before.
 
-	  ASTERISK-29123 #close
+	  Change-Id: Ie63dc558ade6a5a2b969c30a4bd492d63730dc46
 
-	  Change-Id: I4f94eb4544166bca8af4c17fd11edee3c6980620
+2020-08-24 16:26 +0000 [3c4a1722b6]  Kevin Harwell <kharwell@digium.com>
 
-2020-10-06 10:32 +0000 [412b385de5]  Joshua C. Colp <jcolp@sangoma.com>
+	* chan_pjsip: disallow PJSIP_SEND_SESSION_REFRESH pre-answer execution
 
-	* res_pjsip: Adjust outgoing offer call pref.
+	  This patch makes it so if the PJSIP_SEND_SESSION_REFRESH dialplan function
+	  is called on a channel prior to answering a warning is issued and the
+	  function returns unsuccessful.
 
-	  This changes the outgoing offer call preference
-	  default option to match the behavior of previous
-	  versions of Asterisk.
+	  ASTERISK-28878 #close
 
-	  The additional advanced codec negotiation options
-	  have also been removed from the sample configuration
-	  and marked as reserved for future functionality in
-	  XML documentation.
+	  Change-Id: I053f767d10cf3b2b898fa9e3e7c35ff07e23c9bb
 
-	  The codec preference options have also been fixed to
-	  enforce local codec configuration.
+2020-08-27 05:31 +0000 [28bae5e901]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  ASTERISK-29109
+	* pbx: Fix hints deadlock between reload and ExtensionState.
 
-	  Change-Id: Iad19347bd5f3d89900c15ecddfebf5e20950a1c2
+	  When the ExtensionState AMI action is executed on a pattern matched
+	  hint it can end up adding a new hint if one does not already exist.
+	  This results in a locking order of contexts -> hints -> contexts.
 
-2020-08-28 16:32 +0000 [6255e7976c]  Kevin Harwell <kharwell@digium.com>
+	  If at the same time a reload is occurring and adding its own hint
+	  it will have a locking order of hints -> contexts.
 
-	* Logging: Add debug logging categories
+	  This results in a deadlock as one thread wants a lock on contexts
+	  that the other has, and the other thread wants a lock on hints
+	  that the other has.
 
-	  Added debug logging categories that allow a user to output debug
-	  information based on a specified category. This lets the user limit,
-	  and filter debug output to data relevant to a particular context,
-	  or topic. For instance the following categories are now available for
-	  debug logging purposes:
+	  This change enforces a hints -> contexts locking order by explicitly
+	  locking hints in the places where a hint is added when queried for.
+	  This matches the order seen through normal adding of hints.
 
-	    dtls, dtls_packet, ice, rtcp, rtcp_packet, rtp, rtp_packet,
-	    stun, stun_packet
+	  ASTERISK-29046
 
-	  These debug categories can be enable/disable via an Asterisk CLI command.
+	  Change-Id: I49f027f4aab5d2d50855ae937bcf5e2fd8bfc504
 
-	  While this overrides, and outputs debug data, core system debugging is
-	  not affected by this patch. Statements still output at their appropriate
-	  debug level. As well backwards compatibility has been maintained with
-	  past debug groups that could be enabled using the CLI (e.g. rtpdebug,
-	  stundebug, etc.).
+2020-08-14 11:13 +0000 [54ddf19141]  George Joseph <gjoseph@digium.com>
 
-	  ASTERISK-29054 #close
+	* logger.c: Added a new log formatter called "plain"
 
-	  Change-Id: I6e6cb247bb1f01dbf34750b2cd98e5b5b41a1849
-	  (cherry picked from commit 56028426de0692e8e36167251053c91b96e97c41)
+	  Added a new log formatter called "plain" that always prints
+	  file, function and line number if available (even for verbose
+	  messages) and never prints color control characters.  It also
+	  doesn't apply any special formatting for verbose messages.
+	  Most suitable for file output but can be used for other channels
+	  as well.
 
-2020-09-30 15:00 +0000 [a6faa53af0]  Sean Bright <sean.bright@gmail.com>
+	  You use it in logger.conf like so:
+	  debug => [plain]debug
+	  console => [plain]error,warning,debug,notice,pjsip_history
+	  messages => [plain]warning,error,verbose
 
-	* tcptls.c: Don't close TCP client file descriptors more than once
+	  Change-Id: I4fdfe4089f66ce2f9cb29f3005522090dbb5243d
 
-	  ASTERISK-28430 #close
+2020-08-21 16:53 +0000 [5b9ac90531]  Nickolay Shmyrev <nshmyrev@alphacephei.com>
 
-	  Change-Id: Ib556b0a0c95cca939e956886214ec8d828d89606
+	* res_speech: Bump reference on format object
 
-2020-10-05 10:44 +0000 [7ced144867]  Jean Aunis <jean.aunis@prescom.fr>
+	  Properly bump reference on format object to avoid memory corruption on double free
 
-	* resource_endpoints.c: memory leak when providing a 404 response
+	  ASTERISK-29040 #close
 
-	  When handling a send_message request to a non-existing endpoint, the response's
-	  body is overriden and not properly freed.
+	  Change-Id: Ic5a7faabfe2ef965ddb024186e1de7ca4542e2a3
 
-	  ASTERISK-29108
+2020-07-22 03:45 +0000 [04051b324b]  Torrey Searle <tsearle@voxbone.com>
 
-	  Change-Id: Ie1d3d70065f80793445b60f5e4a7eb31b4b9c5c8
+	* res_pjsip_diversion: handle 181
 
-2020-09-29 19:57 +0000 [abee490639]  Sean Bright <sean.bright@gmail.com>
+	  Adapt the response handler so it also called when 181 is received.
+	  In the case 181 is received, also generate the 181 response.
 
-	* app_voicemail.c: Document VMSayName interruption behavior
+	  ASTERISK-29001 #close
 
-	  ASTERISK-26424 #close
+	  Change-Id: I73cfee46a8ca85371280ebdb38674f8fde7510df
 
-	  Change-Id: I797ad0ed302d0b3d2c90543eff5b7207ed08ecf0
+2020-08-21 09:17 +0000 [c925ed0eb9]  Sean Bright <sean.bright@gmail.com>
 
-2020-09-29 13:04 +0000 [5a0b19a4f3]  Sean Bright <sean.bright@gmail.com>
+	* app_voicemail: Process urgent messages with mailcmd
 
-	* pbx.c: On error, ast_add_extension2_lockopt should always free 'data'
+	  Rather than putting messages into INBOX and then moving them to Urgent
+	  later, put them directly in to the Urgent folder. This prevents
+	  mailcmd from being skipped.
 
-	  In the event that the desired extension already exists,
-	  ast_add_extension2_lockopt() will free the 'data' it is passed before
-	  returning an error, so we should not be freeing it ourselves.
+	  ASTERISK-27273 #close
 
-	  Additionally, there were two places where ast_add_extension2_lockopt()
-	  could return an error without also freeing the 'data' pointer, so we
-	  add that.
+	  Change-Id: I49934e093290d308506ab8d45a40ef705c5ae4f5
 
-	  ASTERISK-29097 #close
+2020-08-21 00:10 +0000 [b2bd38a4f0]  Evandro César Arruda <ecarruda@gmail.com>
 
-	  Change-Id: I904707aae55169feda050a5ed7c6793b53fe6eae
+	* app_queue: Member lastpause time reseting
 
-2020-09-24 13:46 +0000 [4a049ad510]  George Joseph <gjoseph@digium.com>
+	  This fixes the reseting members lastpause problem when realtime members is being used,
+	  the function rt_handle_member_record was forcing the reset members lastpause because it
+	  does not exist in realtime
 
-	* app_confbridge/bridge_softmix:  Add ability to force estimated bitrate
+	  ASTERISK-29034 #close
 
-	  app_confbridge now has the ability to set the estimated bitrate on an
-	  SFU bridge.  To use it, set a bridge profile's remb_behavior to "force"
-	  and set remb_estimated_bitrate to a rate in bits per second.  The
-	  remb_estimated_bitrate parameter is ignored if remb_behavior is something
-	  other than "force".
+	  Change-Id: Ic9107e4456732a1f78412a32adb2ef87f5da40b5
 
-	  Change-Id: Idce6464ff014a37ea3b82944452e56cc4d75ab0a
+2020-08-18 04:36 +0000 [71ceefa75d]  Joshua C. Colp <jcolp@sangoma.com>
 
-2020-09-23 04:05 +0000 [08ccfd4588]  Jasper van der Neut <jasper@isotopic.nl>
+	* res_pjsip_session: Don't aggressively terminate on failed re-INVITE.
 
-	* channels: Don't dereference NULL pointer
+	  Per the RFC when an outgoing re-INVITE is done we should
+	  only terminate the dialog if a 481 or 408 is received.
 
-	  Check result of ast_translator_build_path against NULL before dereferencing.
+	  ASTERISK-29033
 
-	  ASTERISK-29091
+	  Change-Id: I6c3ff513aa41005d02de0396ba820083e9b18503
 
-	  Change-Id: Ia3538ea190bd371f70c9dd49984b021765691b29
+2020-08-19 12:29 +0000 [3553192900]  Sean Bright <sean.bright@gmail.com>
 
-2020-09-22 22:39 +0000 [4499fbc819]  Holger Hans Peter Freyther <holger@moiji-mobile.com>
+	* bridge_channel: Ensure text messages are zero terminated
 
-	* res_pjsip_sdp_rtp: Fix accidentally native bridging calls
+	  T.140 data in RTP is not zero terminated, so when we are queuing a text
+	  frame on a bridge we need to ensure that we are passing a zero
+	  terminated string.
 
-	  Stop advertising RFC2833 support on the rtp_engine when DTMF mode is
-	  auto but no tel_event was found inside SDP file.
+	  ASTERISK-28974 #close
 
-	  On an incoming call create_rtp will be called and when session->dtmf is
-	  set to AST_SIP_DTMF_AUTO, the AST_RTP_PROPERTY_DTMF will be set without
-	  looking at the SDP file.
+	  Change-Id: Ic10057387ce30b2094613ea67e3ae8c5c431dda3
 
-	  Once get_codecs gets called we move the DTMF mode from RFC2833 to INBAND
-	  but continued to advertise RFC2833 support.
+2020-08-07 09:31 +0000 [057fda460b]  Sean Bright <sean.bright@gmail.com>
 
-	  This meant the native_rtp bridge would falsely consider the two channels
-	  as compatible. In addition to changing the DTMF mode we now set or
-	  remove the AST_RTP_PROPERTY_DTMF.
+	* res_musiconhold.c: Use ast_file_read_dir to scan MoH directory
 
-	  The property is checked in ast_rtp_dtmf_compatible and called by
-	  native_rtp_bridge_compatible.
+	  Two changes of note in this patch:
 
-	  ASTERISK-29051 #close
+	  * Use ast_file_read_dir instead of opendir/readdir/closedir
 
-	  Change-Id: I1e0c1e324598a437932c0b7836bcb626aba8e287
+	  * If the files list should be sorted, do that at the end rather than as
+	    we go which improves performance for large lists
 
-2020-09-28 07:42 +0000 [b3b6b5e9f7]  lvl <digium@lvlconsultancy.nl>
+	  Change-Id: Ic7e9c913c0f85754c99c74c9cf6dd3514b1b941f
 
-	* res_musiconhold: Load all realtime entries, not just the first
+2020-08-19 07:37 +0000 [64ca2d48da]  George Joseph <gjoseph@digium.com>
 
-	  ASTERISK-29099
+	* scope_trace: Added debug messages and added additional macros
 
-	  Change-Id: I45636679c0fb5a5f59114c8741626631a604e8a6
+	  The SCOPE_ENTER and SCOPE_EXIT* macros now print debug messages
+	  at the same level as the scope level.  This allows the same
+	  messages to be printed to the debug log when AST_DEVMODE
+	  isn't enabled.
 
-2020-09-24 09:54 +0000 [c470327e6c]  Torrey Searle <tsearle@voxbone.com>
+	  Also added a few variants of the SCOPE_EXIT macros that will
+	  also call ast_log instead of ast_debug to make it easier to
+	  use scope tracing and still print error messages.
 
-	* res_pjsip_diversion: fix double 181
+	  Change-Id: I7fe55f7ec28069919a0fc0b11a82235ce904cc21
 
-	  Arming response to both AST_SIP_SESSION_BEFORE_REDIRECTING and
-	  AST_SIP_SESSION_BEFORE_MEDIA causes 302 to to be handled twice,
-	  resulting in to 181 being generated.
+2020-08-20 08:32 +0000 [118cb3f0dd]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: I866e5461564644ffb8a5e12b6f1330b50a7b63ab
+	* stream.c:  Added 2 more debugging utils and added pos to stream string
 
-2020-09-24 11:47 +0000 [5929e0ccbd]  Sean Bright <sean.bright@gmail.com>
+	   * Added ast_stream_to_stra and ast_stream_topology_to_stra() macros
+	     which are shortcuts for
+	        ast_str_tmp(256, ast_stream_to_str(stream, &STR_TMP))
 
-	* res_musiconhold: Clarify that playlist mode only supports HTTP(S) URLs
+	   * Added the stream position to the string representation of the
+	     stream.
 
-	  Change-Id: I41e77a04e4a523f4ed61a7a20b738ffd42be441e
+	   * Fixed some formatting in ast_stream_to_str().
 
-2020-09-23 15:20 +0000 [9b08eddf90]  Sean Bright <sean.bright@gmail.com>
+	  Change-Id: Idaf4cb0affa46d4dce58a73a111f35435331cc4b
 
-	* dsp.c: Update calls to ast_format_cmp to check result properly
+2020-02-18 06:30 +0000 [aab666bb9d]  Dennis Buteyn <dennis.buteyn@xorcom.com>
 
-	  ASTERISK-28311 #close
+	* chan_sip: Clear ToHost property on peer when changing to dynamic host
 
-	  Change-Id: Ib1ce8fc1a8752751f5bf3615c59245532dfd9aa2
+	  The ToHost parameter was not cleared when a peer's host value was
+	  changed to dynamic. This causes invites to be sent to the original host.
 
-2020-09-18 15:02 +0000 [d0644faa5a]  Sean Bright <sean.bright@gmail.com>
+	  ASTERISK-29011 #close
 
-	* res_musiconhold: Start playlist after initial announcement
+	  Change-Id: I9678d512741f71baca8f131a65b7523020b07d5c
 
-	  Only track our sample offset if we are playing a non-announcement file,
-	  otherwise we will skip that number of samples when we start playing the
-	  first MoH file.
+2020-07-20 14:39 +0000 [647c53c41f]  George Joseph <gjoseph@digium.com>
 
-	  ASTERISK-24329 #close
+	* ACN: Changes specific to the core
 
-	  Change-Id: Ib6b3c84fcaa1063889ab38ba7e7fc50050a3ccfc
+	  Allow passing a topology from the called channel back to the
+	  calling channel.
 
-2020-09-22 05:05 +0000 [9eeb40af33]  Joshua C. Colp <jcolp@sangoma.com>
+	   * Added a new function ast_queue_answer() that accepts a stream
+	     topology and queues an ANSWER CONTROL frame with it as the
+	     data.  This allows the called channel to indicate its resolved
+	     topology.
 
-	* res_pjsip_session: Fix stream name memory leak.
+	   * Added a new virtual function to the channel tech structure
+	     answer_with_stream_topology() that allows the calling channel
+	     to receive the called channel's topology.  Added
+	     ast_raw_answer_with_stream_topology() that invokes that virtual
+	     function.
 
-	  When constructing a stream name based on the media type
-	  and position the allocated name was not being freed
-	  causing a leak.
+	   * Modified app_dial.c and features.c to grab the topology from the
+	     ANSWER frame queued by the answering channel and send it to
+	     the calling channel with ast_raw_answer_with_stream_topology().
 
-	  Change-Id: I52510863b24a2f531f0a55b440bb2c81844029de
+	   * Modified frame.c to automatically cleanup the reference
+	     to the topology on ANSWER frames.
 
-2020-09-18 08:09 +0000 [28c88e8fe2]  Sean Bright <sean.bright@gmail.com>
+	  Added a few debugging messages to stream.c.
 
-	* func_curl.c: Prevent crash when using CURLOPT(httpheader)
+	  Change-Id: I0115d2ed68d6bae0f87e85abcf16c771bdaf992c
 
-	  Because we use shared thread-local cURL instances, we need to ensure
-	  that the state of the cURL instance is correct before each invocation.
+2020-08-06 12:51 +0000 [3040edcbb1]  cmaj <chris@penguinpbx.com>
 
-	  In the case of custom headers, we were not resetting cURL's internal
-	  HTTP header pointer which could result in a crash if subsequent
-	  requests do not configure custom headers.
+	* Makefile: Fix certified version numbers
 
-	  ASTERISK-29085 #close
+	  Adds sed before awk to produce reasonable ASTERISKVERSIONNUM
+	  on certified versions of Asterisk eg. 16.8-cert3 is 160803
+	  instead of the previous 00800.
 
-	  Change-Id: I8b4ab34038156dfba613030a45f10e932d2e992d
+	  ASTERISK-29021 #close
 
-2020-09-22 05:13 +0000 [957aff751d]  Joshua C. Colp <jcolp@sangoma.com>
+	  Change-Id: Icf241df0ff6db09011b8c936a317a84b0b634e16
 
-	* res_pjsip_session: Fix session reference leak.
+2020-08-06 11:41 +0000 [b7c2205402]  Sean Bright <sean.bright@gmail.com>
 
-	  The ast_sip_dialog_get_session function returns the session
-	  with reference count increased. This was not taken into
-	  account and was causing sessions to remain around when they
-	  should not be.
+	* res_musiconhold.c: Prevent crash with realtime MoH
 
-	  ASTERISK-29089
+	  The MoH class internal file vector is potentially being manipulated by
+	  multiple threads at the same time without sufficient locking. Switch to
+	  a reference counted list and operate on copies where necessary.
 
-	  Change-Id: I430fa721b0a824311a59effec6056e9ec528e3e8
+	  ASTERISK-28927 #close
 
-2020-09-16 08:01 +0000 [2bce21da88]  Michal Hajek <michal.hajek@daktela.com>
+	  Change-Id: I479c5dcf88db670956e8cac177b5826c986b0217
 
-	* res_stasis.c: Add compare function for bridges moh container
+2020-08-06 13:10 +0000 [447f6cc37a]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  Sometimes not play MOH on bridge.
+	* res_pjsip: Fix codec preference defaults.
 
-	  ASTERISK-29081
-	  Reported-by: Michal Hajek <michal.hajek@daktela.com>
+	  When reading in a codec preference configuration option
+	  the value would be set on the respective option before
+	  applying any default adjustments, resulting in the
+	  configuration not being as expected.
 
-	  Change-Id: I760c73e0c9be1d340303b5d1c18a00c4759e8232
+	  This was exposed by the REST API push configuration as
+	  it used the configuration returned by Asterisk to then do
+	  a modification. In the case of codec preferences one of
+	  the options had a transcode value of "unspecified" when the
+	  defaults should have ensured it would be "allow" instead.
 
-2020-09-17 11:40 +0000 [99bd7d95de]  George Joseph <gjoseph@digium.com>
+	  This also renames the options in other places that were
+	  missed.
 
-	* logger.h: Fix ast_trace to respect scope_level
+	  Change-Id: I4ad42e74fdf181be2e17bc75901c62591d403964
 
-	  ast_trace() was always emitting messages when it's level was set to -1
-	  because it was ignoring scope_level.
+2020-08-04 10:51 +0000 [048b12b59d]  Sean Bright <sean.bright@gmail.com>
 
-	  Change-Id: I849c8f4f4613899c37f82be0202024e7d117e506
+	* vector.h: Fix implementation of AST_VECTOR_COMPACT() for empty vectors
+
+	  The assumed behavior of realloc() - that it was effectively a free() if
+	  its second argument was 0 - is Linux specific behavior and is not
+	  guaranteed by either POSIX or the C specification.
 
-2020-09-15 15:44 +0000 [c90c182932]  Sean Bright <sean.bright@gmail.com>
+	  Instead, if we want to resize a vector to 0, do it explicitly.
 
-	* audiosocket: Fix module menuselect descriptions
+	  Change-Id: Ife31d4b510ebab41cb5477fdc7ea4e3138ca8b4f
 
-	  The module description needs to be on the same line as the
-	  AST_MODULE_INFO or it is not parsed correctly.
+2020-06-30 10:40 +0000 [e8c2ce2873]  Michael Neuhauser <mike@firmix.at>
+
+	* pjproject: clone sdp to protect against (nat) modifications
 
-	  Change-Id: I9ba11df1415369790e8656fcb527bb2749373c21
+	  PJSIP, UDP transport with external_media_address and session timers
+	  enabled. Connected to SIP server that is not in local net. Asterisk
+	  initiated the connection and is refreshing the session after 150s
+	  (timeout 300s). The 2nd refresh-INVITE triggered by the pjsip timer has
+	  a malformed IP address in its SDP (garbage string). This only happens
+	  when the SDP is modified by the nat-code to replace the local IP address
+	  with the configured external_media_address.
+	  Analysis: the code to modify the SDP (in
+	  res_pjsip_session.c:session_outgoing_nat_hook() and also (redundantly?)
+	  in res_pjsip_sdp_rtp.c:change_outgoing_sdp_stream_media_address()) uses
+	  the tdata->pool to allocate the replacement string. But the same
+	  pjmedia_sdp_stream that was modified for the 1st refresh-INVITE is also
+	  used for the 2nd refresh-INVITE (because it is stored in pjmedia's
+	  pjmedia_sdp_neg structure). The problem is, that at that moment, the
+	  tdata->pool that holds the stringified external_media_address from the
+	  1. refresh-INVITE has long been reused for something else.
+	  Fix by Sauw Ming of pjproject (see
+	  https://github.com/pjsip/pjproject/pull/2476): the local, potentially
+	  modified pjmedia_sdp_stream is cloned in
+	  pjproject/source/pjsip/src/pjmedia/sip_neg.c:process_answer() and the
+	  clone is stored, thereby detaching from the tdata->pool (which is only
+	  released *after* process_answer())
 
-2020-09-17 13:01 +0000 [fdc13060df]  George Joseph <gjoseph@digium.com>
+	  ASTERISK-28973
+	  Reported-by: Michael Neuhauser
 
-	* bridge_softmix/sfu_topologies_on_join: Ignore topology change failures
+	  Change-Id: I272ac22436076596e06aa51b9fa23fd1c7734a0e
 
-	  When a channel joins a bridge, we do topology change requests on all
-	  existing channels to add the new participant to them.  However the
-	  announcer channel will return an error because it doesn't support
-	  topology in the first place.  Unfortunately, there doesn't seem to be a
-	  reliable way to tell if the error is expected or not so the error is
-	  ignored for all channels.  If the request fails on a "real" channel,
-	  that channel just won't get the new participant's video.
+2020-08-04 14:36 +0000 [9ed6387c14]  Ben Ford <bford@digium.com>
 
-	  Change-Id: Ic95db4683f27d224c1869fe887795d6b9fdea4f0
+	* utils.c: NULL terminate ast_base64decode_string.
 
-2020-09-15 16:16 +0000 [6f32c254be]  Sean Bright <sean.bright@gmail.com>
+	  With the addition of STIR/SHAKEN, the function ast_base64decode_string
+	  was added for convenience since there is a lot of converting done during
+	  the STIR/SHAKEN process. This function returned the decoded string for
+	  you, but did not NULL terminate it, causing some issues (specifically
+	  with MALLOC_DEBUG). Now, the returned string is NULL terminated, and the
+	  documentation has been updated to reflect this.
 
-	* res_pjsip_session.c: Fix build when TEST_FRAMEWORK is not defined
+	  Change-Id: Icdd7d05b323b0c47ff6ed43492937a03641bdcf5
 
-	  Change-Id: Id4852c26e9c412af8e37b5dd3c15da9453ad3276
+2020-07-21 09:17 +0000 [a15e64aaf5]  George Joseph <gjoseph@digium.com>
 
-2020-08-13 03:34 +0000 [83140c9fed]  Torrey Searle <tsearle@voxbone.com>
+	* ACN: Configuration renaming for pjsip endpoint
 
-	* res_pjsip_diversion: implement support for History-Info
+	  This change renames the codec preference endpoint options.
+	  incoming_offer_codec_prefs becomes codec_prefs_incoming_offer
+	  to keep the options together when showing an endpoint.
 
-	  Implemention of History-Info capable of interworking with Diversion
-	  Header following RFC7544
+	  Change-Id: I6202965b4723777f22a83afcbbafcdafb1d11c8d
 
-	  ASTERISK-29027 #close
+2020-07-20 13:05 +0000 [deaa3742dc]  Ben Ford <bford@digium.com>
 
-	  Change-Id: I2296369582d4b295c5ea1e60bec391dd1d318fa6
+	* res_stir_shaken: Fix memory allocation error in curl.c
 
-2020-09-14 13:23 +0000 [4964302984]  Sean Bright <sean.bright@gmail.com>
+	  Fixed a memory allocation that was not passing in the correct size for
+	  the struct in curl.c.
 
-	* format_cap: Perform codec lookups by pointer instead of name
+	  Change-Id: I5fb92fbbe84b075fa6aefa2423786df80e114c3a
 
-	  ASTERISK-28416 #close
+2020-07-23 14:47 +0000 [1f78ee9d0f]  George Joseph <gjoseph@digium.com>
 
-	  Change-Id: I069420875ebdbcaada52d92599a5f7de3cb2cdf4
+	* res_pjsip_session: Ensure reused streams have correct bundle group
 
-2020-09-11 11:09 +0000 [cc71be0078]  George Joseph <gjoseph@digium.com>
+	  When a bundled stream is removed, its bundle_group is reset to -1.
+	  If that stream is later reused, the bundle parameters on session
+	  media need to be reset correctly it could mistakenly be rebundled
+	  with a stream that was removed and never reused.  Since the removed
+	  stream has no rtp instance, a crash will result.
 
-	* res_pjsip_session: Fix issue with COLP and 491
+	  Change-Id: Ie2b792220f9291587ab5f9fd123145559dba96d7
 
-	  The recent 491 changes introduced a check to determine if the active
-	  and pending topologies were equal and to suppress the re-invite if they
-	  were. When a re-invite is sent for a COLP-only change, the pending
-	  topology is NULL so that check doesn't happen and the re-invite is
-	  correctly sent. Of course, sending the re-invite sets the pending
-	  topology.  If a 491 is received, when we resend the re-invite, the
-	  pending topology is set and since we didn't request a change to the
-	  topology in the first place, pending and active topologies are equal so
-	  the topologies-equal check causes the re-invite to be erroneously
-	  suppressed.
+2020-07-22 04:41 +0000 [921b1a02c4]  Joshua C. Colp <jcolp@sangoma.com>
 
-	  This change checks if the topologies are equal before we run the media
-	  state resolver (which recreates the pending topology) so that when we
-	  do the final topologies-equal check we know if this was a topology
-	  change request.  If it wasn't a change request, we don't suppress
-	  the re-invite even though the topologies are equal.
+	* res_pjsip_registrar: Don't specify an expiration for static contacts.
 
-	  ASTERISK-29014
+	  Statically configured contacts on an AOR don't have an expiration
+	  time so when adding them to the resulting 200 OK if an endpoint
+	  registers ensure they are marked as such.
 
-	  Change-Id: Iffd7dd0500301156a566119ebde528d1a9573314
+	  ASTERISK-28995
 
-2020-08-20 15:09 +0000 [ad4f2a8c99]  George Joseph <gjoseph@digium.com>
+	  Change-Id: I9f0e45eb2ccdedc9a0df5358634a19ccab0ad596
 
-	* debugging:  Add enough to choke a mule
+2020-07-13 15:06 +0000 [7d96b3e437]  Sean Bright <sean.bright@gmail.com>
 
-	  Added to:
-	   * bridges/bridge_softmix.c
-	   * channels/chan_pjsip.c
-	   * include/asterisk/res_pjsip_session.h
-	   * main/channel.c
-	   * res/res_pjsip_session.c
+	* utf8.c: Add UTF-8 validation and utility functions
 
-	  There NO functional changes in this commit.
+	  There are various places in Asterisk - specifically in regards to
+	  database integration - where having some kind of UTF-8 validation would
+	  be beneficial. This patch adds:
 
-	  Change-Id: I06af034d1ff3ea1feb56596fd7bd6d7939dfdcc3
+	  * Functions to validate that a given string contains only valid UTF-8
+	    sequences.
 
-2020-08-20 11:21 +0000 [d4f3b17dd3]  George Joseph <gjoseph@digium.com>
+	  * A function to copy a string (similar to ast_copy_string) stopping when
+	    an invalid UTF-8 sequence is encountered.
 
-	* res_pjsip_session:  Handle multi-stream re-invites better
+	  * A UTF-8 validator that allows for progressive validation.
 
-	  When both Asterisk and a UA send re-invites at the same time, both
-	  send 491 "Transaction in progress" responses to each other and back
-	  off a specified amount of time before retrying. When Asterisk
-	  prepares to send its re-invite, it sets up the session's pending
-	  media state with the new topology it wants, then sends the
-	  re-invite.  Unfortunately, when it received the re-invite from the
-	  UA, it partially processed the media in the re-invite and reset
-	  the pending media state before sending the 491 losing the state it
-	  set in its own re-invite.
+	  All of this is based on the excellent UTF-8 decoder by Björn Höhrmann.
+	  More information is available here:
 
-	  Asterisk also was not tracking re-invites received while an existing
-	  re-invite was queued resulting in sending stale SDP with missing
-	  or duplicated streams, or no re-invite at all because we erroneously
-	  determined that a re-invite wasn't needed.
+	      https://bjoern.hoehrmann.de/utf-8/decoder/dfa/
 
-	  There was also an issue in bridge_softmix where we were using a stream
-	  from the wrong topology to determine if a stream was added.  This also
-	  caused us to erroneously determine that a re-invite wasn't needed.
+	  The API was written in such a way that should allow us to replace the
+	  implementation later should we determine that we need something more
+	  comprehensive.
 
-	  Regardless of how the delayed re-invite was triggered, we need to
-	  reconcile the topology that was active at the time the delayed
-	  request was queued, the pending topology of the queued request,
-	  and the topology currently active on the session.  To do this we
-	  need a topology resolver AND we need to make stream named unique
-	  so we can accurately tell what a stream has been added or removed
-	  and if we can re-use a slot in the topology.
+	  Change-Id: I3555d787a79e7c780a7800cd26e0b5056368abf9
 
-	  Summary of changes:
+2020-07-10 18:14 +0000 [c10ed8d4d6]  sungtae kim <sungtae@messagebird.com>
 
-	   * bridge_softmix:
-	     * We no longer reset the stream name to "removed" in
-	       remove_all_original_streams().  That was causing  multiple streams
-	       to have the same name and wrecked the checks for duplicate streams.
+	* stasis_bridge.c: Fixed wrong video_mode shown
 
-	     * softmix_bridge_stream_sources_update() was checking the old_stream
-	       to see if it had the softmix prefix and not considering the stream
-	       as "new" if it did.  If the stream in that slot has something in it
-	       because another re-invite happened, then that slot in old might
-	       have a softmix stream but the same stream in new might actually
-	       be a new one.  Now we check the new_stream's name instead of
-	       the old_stream's.
+	  Currently, if the bridge has created by the ARI, the video_mode
+	  parameter was
+	  not shown in the BridgeCreated event correctly.
 
-	   * stream:
-	     * Instead of using plain media type name ("audio", "video", etc) as
-	       the default stream name, we now append the stream position to it
-	       to make it unique.  We need to do this so we can distinguish multiple
-	       streams of the same type from each other.
+	  Fixed it and added video_mode shown in the 'bridge show <bridge id>'
+	  cli.
 
-	     * When we set a stream's state to REMOVED, we no longer reset its
-	       name to "removed" or destroy its metadata.  Again, we need to
-	       do this so we can distinguish multiple streams of the same
-	       type from each other.
+	  ASTERISK-28987
 
-	   * res_pjsip_session:
-	     * Added resolve_refresh_media_states() that takes in 3 media states
-	       and creates an up-to-date pending media state that includes the changes
-	       that might have happened while a delayed session refresh was in the
-	       delayed queue.
+	  Change-Id: I8c205126724e34c2bdab9380f523eb62478e4295
 
-	     * Added is_media_state_valid() that checks the consistency of
-	       a media state and returns a true/false value. A valid state has:
-	       * The same number of stream entries as media session entries.
-	           Some media session entries can be NULL however.
-	       * No duplicate streams.
-	       * A valid stream for each non-NULL media session.
-	       * A stream that matches each media session's stream_num
-	         and media type.
+2020-07-20 13:17 +0000 [b5bb4a7a0d]  Sean Bright <sean.bright@gmail.com>
 
-	     * Updated handle_incoming_sdp() to set the stream name to include the
-	       stream position number in the name to make it unique.
+	* vector.h: Add AST_VECTOR_SORT()
 
-	     * Updated the ast_sip_session_delayed_request structure to include both
-	       the pending and active media states and updated the associated delay
-	       functions to process them.
+	  Allows a vector to be sorted in-place, rather than only during
+	  insertion.
 
-	     * Updated sip_session_refresh() to accept both the pending and active
-	       media states that were in effect when the request was originally queued
-	       and to pass them on should the request need to be delayed again.
+	  Change-Id: I22cba9ddf556a7e44dacc53c4431bd81dd2fa780
 
-	     * Updated sip_session_refresh() to call resolve_refresh_media_states()
-	       and substitute its results for the pending state passed in.
+2020-07-16 08:41 +0000 [e1d30f3e6c]  George Joseph <gjoseph@digium.com>
 
-	     * Updated sip_session_refresh() with additional debugging.
+	* CI: Force publishAsteriskDocs to use python2
 
-	     * Updated session_reinvite_on_rx_request() to simply return PJ_FALSE
-	       to pjproject if a transaction is in progress.  This stops us from
-	       creating a partial pending media state that would be invalid later on.
+	  Change-Id: I7d951e75ad2d472fa096647dfb55670b11105e23
 
-	     * Updated reschedule_reinvite() to clone both the current pending and
-	       active media states and pass them to delay_request() so the resolver
-	       can tell what the original intention of the re-invite was.
+2020-07-22 12:57 +0000 [9f641483e6]  Joshua C. Colp <jcolp@sangoma.com>
 
-	     * Added a large unit test for the resolver.
+	* websocket / pjsip: Increase maximum packet size.
 
-	  ASTERISK-29014
+	  When dealing with a lot of video streams on WebRTC
+	  the resulting SDPs can grow to be quite large. This
+	  effectively doubles the maximum size to allow more
+	  streams to exist.
 
-	  Change-Id: Id3440972943c611a15f652c6c569fa0e4536bfcb
+	  The res_http_websocket module has also been changed
+	  to use a buffer on the session for reading in packets
+	  to ensure that the stack space usage is not excessive.
 
-2020-08-31 07:21 +0000 [1fd12b88c7]  Sungtae Kim <pchero21@gmail.com>
+	  Change-Id: I31d4351d70c8e2c11564807a7528b984f3fbdd01
 
-	* realtime: Increased reg_server character size
+2020-07-15 09:05 +0000 [9c3b57822a]  George Joseph <gjoseph@digium.com>
 
-	  Currently, the ps_contacts table's reg_server column in realtime database type is varchar(20).
-	  This is fine for normal cases, but if the hostname is longer than 20, it returns error and then
-	  failed to register the contact address of the peer.
+	* Prepare master for the next Asterisk version
 
-	  Normally, 20 characters limitation for the hostname is fine, but with the cloud env.
-	  So, increased the size to 255.
+	  * Updated AMI version to 8.0.0
+	  * Updated ARI version to 7.0.0
+	  * Update make_ari_stubs.py to "Asterisk 19"
 
-	  ASTERISK-29056
+	  Change-Id: I51fb38c2e29f2db785f64a8bbd5565d56bea5af5
 
-	  Change-Id: Iac52c8c35030303cfa551bb39f410b33bffc507d
+2020-07-13 15:42 +0000 [c3588d9c0b]  Sean Bright <sean.bright@gmail.com>
 
-2020-08-30 15:42 +0000 [a0d41a27d4]  Sungtae Kim <pchero21@gmail.com>
+	* acl.c: Coerce a NULL pointer into the empty string
 
-	* res_stasis.c: Added video_single option for bridge creation
+	  If an ACL is misconfigured in the realtime database (for instance, the
+	  "rule" is blank) and Asterisk attempts to read the ACL, Asterisk will
+	  crash.
 
-	  Currently, it was not possible to create bridge with video_mode single.
-	  This made hard to put the bridge in a vidoe_single mode.
-	  So, added video_single option for Bridge creation using the ARI.
-	  This allows create a bridge with video_mode single.
+	  ASTERISK-28978 #close
 
-	  ASTERISK-29055
+	  Change-Id: Ic1536c4df856231bfd2da00128f7822224d77610
 
-	  Change-Id: I43e720e5c83fc75fafe10fe22808ae7f055da2ae
+2020-07-13 04:41 +0000 [f1d7de121f]  Joshua C. Colp <jcolp@sangoma.com>
 
-2020-08-31 11:14 +0000 [7eaae4e7b6]  Ben Ford <bford@digium.com>
+	* pjsip: Include timer patch to prevent cancelling timer 0.
 
-	* Bridging: Use a ref to bridge_channel's channel to prevent crash.
+	  I noticed this while looking at another issue and brought
+	  it up with Teluu. It was possible for an uninitialized timer
+	  to be cancelled, resulting in the invalid timer id of 0
+	  being placed into the timer heap causing issues.
 
-	  There's a race condition with bridging where a bridge can be torn down
-	  causing the bridge_channel's ast_channel to become NULL when it's still
-	  needed. This particular case happened with attended transfers, but the
-	  crash occurred when trying to publish a stasis message. Now, the
-	  bridge_channel is locked, a ref to the ast_channel is obtained, and that
-	  ref is passed down the chain.
+	  This change is a backport from the pjproject repository
+	  preventing this from happening.
 
-	  Change-Id: Ic48715c0c041615d17d286790ae3e8c61bb28814
+	  Change-Id: I1ba318b1f153a6dd7458846396e2867282b428e7
 
 2020-09-09 15:43 +0000  Asterisk Development Team <asteriskteam@digium.com>
 
diff --git a/LICENSE b/LICENSE
index f77d01fd0d96d65238db0285510d9c42e36f2ec5..c552478e9c6362ed08f88c59a433df415c555c6c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -45,7 +45,7 @@ redistribution of Asterisk source code obtained from Digium, you
 should contact our licensing department to determine the necessary
 steps you must take. For more information on this policy, please read:
 
-https://www.sangoma.com/wp-content/uploads/Sangoma-Trademark-Policy.pdf
+https://www.sangoma.com/wp-content/uploads/Sangoma-Trademark-Policy-1.pdf
 
 If you have any questions regarding our licensing policy, please
 contact us:
diff --git a/Makefile b/Makefile
index 8c9498cdb98170abe1a0d85fc441b2af04822934..fb06f714d326908c45bb7865fd2484f5dbd6de89 100644
--- a/Makefile
+++ b/Makefile
@@ -327,6 +327,9 @@ else
 SUBMAKE:=$(MAKE) --quiet --no-print-directory
 endif
 
+mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
+mkfile_dir := $(dir $(mkfile_path))
+
 # $(MAKE) is printed in several places, and we want it to be a
 # fixed size string. Define a variable whose name has also the
 # same size, so we can easily align text.
@@ -558,9 +561,9 @@ bininstall: _all installdirs $(SUBDIRS_INSTALL) main-bininstall
 	$(INSTALL) -m 755 contrib/scripts/astversion "$(DESTDIR)$(ASTSBINDIR)/"
 	$(INSTALL) -m 755 contrib/scripts/astgenkey "$(DESTDIR)$(ASTSBINDIR)/"
 	$(INSTALL) -m 755 contrib/scripts/autosupport "$(DESTDIR)$(ASTSBINDIR)/"
-	if [ ! -f /sbin/launchd ]; then \
-		./build_tools/install_subst contrib/scripts/safe_asterisk "$(DESTDIR)$(ASTSBINDIR)/safe_asterisk"; \
-	fi
+ifneq ($(HAVE_SBIN_LAUNCHD),1)
+	./build_tools/install_subst contrib/scripts/safe_asterisk "$(DESTDIR)$(ASTSBINDIR)/safe_asterisk";
+endif
 
 ifneq ($(DISABLE_XMLDOC),yes)
 	$(INSTALL) -m 644 doc/core-*.xml "$(DESTDIR)$(ASTDATADIR)/documentation"
@@ -693,7 +696,17 @@ ifneq ($(filter ~%,$(DESTDIR)),)
 	@exit 1
 endif
 
-install: badshell bininstall datafiles
+versioncheck:
+ifeq ($(ASTERISKVERSION),UNKNOWN__git_check_fail)
+	@echo "Asterisk Version is unknown due to a git error. If you are running make"
+	@echo "as a different user than the project owner, this can be resolved by"
+	@echo "running the following command as the user currently executing make: "$$USER
+	@echo "git config --global --add safe.directory "$(mkfile_dir:/=)
+	@exit 1
+endif
+
+
+install: badshell versioncheck bininstall datafiles
 	@if [ -x /usr/sbin/asterisk-post-install ]; then \
 		/usr/sbin/asterisk-post-install "$(DESTDIR)" . ; \
 	fi
@@ -878,6 +891,12 @@ ifeq ($(AST_DEVMODE),yes)
 endif
 ifeq ($(ASTERISKVERSION),UNKNOWN__and_probably_unsupported)
 	@echo "Asterisk Version is unknown, not configuring Doxygen PROJECT_NUMBER."
+else ifeq ($(ASTERISKVERSION),UNKNOWN__git_check_fail)
+	@echo "Asterisk Version is unknown due to a git error. If you are running make"
+	@echo "as a different user than the project owner, this can be resolved by"
+	@echo "running the following command as the user currently executing make: "$$USER
+	@echo "git config --global --add safe.directory "$(mkfile_dir:/=)
+	@echo "not configuring Doxygen PROJECT_NUMBER."
 else
 	@echo "PROJECT_NUMBER = $(ASTERISKVERSION)" >> doc/Doxyfile
 endif
@@ -968,6 +987,7 @@ sounds:
 	@$(MAKE) clean
 	@[ -f "$(DESTDIR)$(ASTDBDIR)/astdb.sqlite3" ] || [ ! -f "$(DESTDIR)$(ASTDBDIR)/astdb" ] || [ ! -f menuselect.makeopts ] || grep -q MENUSELECT_UTILS=.*astdb2sqlite3 menuselect.makeopts || (sed -i.orig -e's/MENUSELECT_UTILS=\(.*\)/MENUSELECT_UTILS=\1 astdb2sqlite3/' menuselect.makeopts && echo "Updating menuselect.makeopts to include astdb2sqlite3" && echo "Original version backed up to menuselect.makeopts.orig")
 
+
 $(SUBDIRS_UNINSTALL):
 	+@DESTDIR="$(DESTDIR)" ASTSBINDIR="$(ASTSBINDIR)" ASTDATADIR="$(ASTDATADIR)" $(SUBMAKE) -C $(@:-uninstall=) uninstall
 
@@ -1020,6 +1040,7 @@ uninstall-all: _uninstall uninstall-headers
 	rm -rf "$(DESTDIR)$(ASTSPOOLDIR)"
 	rm -rf "$(DESTDIR)$(ASTETCDIR)"
 	rm -rf "$(DESTDIR)$(ASTLOGDIR)"
+	rm -rf "$(DESTDIR)$(ASTCACHEDIR)"
 
 menuconfig: menuselect
 
diff --git a/Makefile.rules b/Makefile.rules
index 934e44a92d41e28b9ecd89b160427683dc069b67..7b508e6ab2cd292cc2ec25f7d79a982941b3c020 100644
--- a/Makefile.rules
+++ b/Makefile.rules
@@ -204,4 +204,19 @@ endif
 	$(ECHO_PREFIX) echo "   [LD] $^ -> $@"
 	$(CMD_PREFIX) $(CXX) -o $@ $(PTHREAD_CFLAGS) $(_ASTLDFLAGS) $^ $(CXX_LIBS) $(ASTLDFLAGS)
 
+# These CC commands just create an object file with the input file embedded in it.
+# It can be access from code as follows:
+# If your input file is named abc_def.xml...
+#
+# extern const uint8_t _binary_abc_def_xml_start[];
+# extern const uint8_t _binary_abc_def_xml_end[];
+# extern const size_t _binary_abc_def_xml_size;
+%.o: %.xml
+	$(ECHO_PREFIX) echo "   [LD] $^ -> $@"
+	$(CMD_PREFIX) $(CC) -g -Wl,-znoexecstack -nostartfiles  -nodefaultlibs -nostdlib -r -Wl,-b,binary -o $@ $^
+
+%.o: %.xslt
+	$(ECHO_PREFIX) echo "   [LD] $^ -> $@"
+	$(CMD_PREFIX) $(CC) -g -Wl,-znoexecstack -nostartfiles  -nodefaultlibs -nostdlib -r -Wl,-b,binary -o $@ $^
+
 dist-clean:: clean
diff --git a/README.md b/README.md
index 29cb47a394b964db1d081fb5c83f97b1cbe83bb8..0eb4b879a524c59324d0ae1b286db5b0358e4fe8 100644
--- a/README.md
+++ b/README.md
@@ -91,7 +91,10 @@ guides in the [configs] directory.
 2. Run `./configure`
 
   Execute the configure script to guess values for system-dependent
-variables used during compilation.
+variables used during compilation. If the script indicates that some required 
+components are missing, you can run `./contrib/scripts/install_prereq install`
+to install the necessary components. Note that this will install all dependencies for every functionality of Asterisk. After running the script, you will need
+to rerun `./configure`.
 
 3. Run `make menuselect` _\[optional]_
 
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000000000000000000000000000000000000..5386952efcf12387d52e5355a74c7e9eaeed0e15
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,9 @@
+# Security Policy
+
+## Supported Versions
+
+The Asterisk project maintains a [wiki page](https://wiki.asterisk.org/wiki/display/AST/Asterisk+Versions) of releases. Each version is listed with its release date, security fix only date, and end of life date. Consult this wiki page to see if the version of Asterisk you are reporting a security vulnerability against is still supported.
+
+## Reporting a Vulnerability
+
+To report a vulnerability use the "Report a vulnerability" button under the "Security" tab of this project.
diff --git a/UPGRADE.txt b/UPGRADE.txt
index be08f73e0e9faf9ed3301a15b0a9f2fa6744b9d4..f453d9e29679e09ea9f078409589998d199c18de 100644
--- a/UPGRADE.txt
+++ b/UPGRADE.txt
@@ -1,3 +1,6 @@
+===== WARNING, THIS FILE IS OBSOLETE AND WILL BE REMOVED IN A FUTURE VERSION =====
+See 'Upgrade Notes' in the CHANGES file
+
 ===========================================================
 ===
 === THIS FILE IS AUTOMATICALLY GENERATED DURING THE RELEASE
@@ -19,7 +22,55 @@
 ===========================================================
 
 ------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.10.0 to Asterisk 18.11.0 ----------
+--- Functionality changes from Asterisk 20.1.0 to Asterisk 20.2.0 ------------
+------------------------------------------------------------------------------
+
+app_playback
+------------------
+ * In Asterisk 11, if a channel was redirected away during Playback(),
+   the PLAYBACKSTATUS variable would be set to SUCCESS. In Asterisk 12
+   (specifically commit 7d9871b3940fa50e85039aef6a8fb9870a7615b9) that
+   behavior was inadvertently changed and the same operation would result
+   in the PLAYBACKSTATUS variable being set to FAILED. The Asterisk 11
+   behavior has been restored.
+
+------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 20.0.0 to Asterisk 20.1.0 ------------
+------------------------------------------------------------------------------
+
+AMI (Asterisk Manager Interface)
+------------------
+ * Previously, GetConfig and UpdateConfig were able to access files outside of
+   the Asterisk configuration directory. Now this access is put behind the
+   live_dangerously configuration option in asterisk.conf, which is disabled by
+   default. If access to configuration files outside of the Asterisk configuation
+   directory is required via AMI, then the live_dangerously configuration option
+   must be set to yes.
+
+------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 19.0.0 to Asterisk 20.0.0 ------------
+------------------------------------------------------------------------------
+
+res_crypto
+------------------
+ * In addition to only paying attention to files ending with .key or .pub
+   in the keys directory, we now also ignore any files which aren't regular
+   files.
+
+------------------------------------------------------------------------------
+--- New functionality introduced in Asterisk 20.0.0 --------------------------
+------------------------------------------------------------------------------
+
+res_monitor
+------------------
+ * This module is no longer built by default in
+   accordance with the Module Deprecation Policy.
+   If you require this functionality you will need
+   to enable it for building in menuselect. Note
+   that in the future res_monitor will be removed.
+
+------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 19.0.0 to Asterisk 20.0.0 ------------
 ------------------------------------------------------------------------------
 
 AMI
@@ -31,10 +82,6 @@ AMI
    attribute names that start with a digit with an underscore ('_') to
    prevent XML validation failures.
 
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.8.0 to Asterisk 18.9.0 ------------
-------------------------------------------------------------------------------
-
 STIR/SHAKEN
 ------------------
  * The STIR/SHAKEN configuration option has been split into
@@ -43,10 +90,6 @@ STIR/SHAKEN
    attestation on the endpoint, and verify will only perform
    verification on the endpoint.
 
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.7.0 to Asterisk 18.8.0 ------------
-------------------------------------------------------------------------------
-
 chan_iax2
 ------------------
  * Encryption is now supported for RSA authentication.
@@ -63,8 +106,44 @@ chan_iax2
    If both the peer and user are patches, no crash occurs.
    Existing good configurations should continue to work.
 
+res_http_media_cache
+------------------
+ * When fetching a file for playback from a URL, Asterisk will now first
+   use the value of the Content-Type header in the HTTP response to
+   determine the format of the audio data, and only if it is unable to do
+   that will it attempt to parse the URL and extract the extension from
+   the path portion. Previously Asterisk would first look at the end of
+   the URL, which may have included query string parameters or a URL
+   fragment, which was error prone.
+
+res_pjsip
+------------------
+ * The 'async_operations' setting on transports is no longer
+   obeyed and instead is always set to 1. This is due to the
+   functionality not being applicable to Asterisk and causing
+   excess unnecessary memory usage. This setting will now be
+   ignored but can also be removed from the configuration file.
+
 ------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.4.0 to Asterisk 18.5.0 ------------
+--- New functionality introduced in Asterisk 19.0.0 --------------------------
+------------------------------------------------------------------------------
+
+Log Rotate
+------------------
+ * The sample logger files have been changed to have .log as their file
+   extension. This was done so that when attached to issues on the issue
+   tracker, they are able to be opened in the browser for convenience.
+   Because of this, the asterisk.logrotate script has been updated to look
+   for .log extensions instead of no extension for files such as full
+   and messages.
+
+chan_sip
+------------------
+ * chan_sip is no longer built by default. To build it, make sure to
+   enable it when running 'make menuselect'
+
+------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 18.0.0 to Asterisk 19.0.0 ------------
 ------------------------------------------------------------------------------
 
 STIR/SHAKEN
@@ -81,20 +160,12 @@ STIR/SHAKEN
    you have in your stir_shaken.conf will need to be removed
    for the module to read in certificates.
 
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.3.0 to Asterisk 18.4.0 ------------
-------------------------------------------------------------------------------
-
 menuselect
 ------------------
  * menuselect --enable, --disable, --enable-category and --disable-category will
    now fail with a non-zero exit code instead of silently failing if an invalid
    option or category is specified.
 
-------------------------------------------------------------------------------
---- Functionality changes from Asterisk 18.2.2 to Asterisk 18.3.0 ------------
-------------------------------------------------------------------------------
-
 res_srtp
 ------------------
  * SRTP replay protection has been added to res_srtp and
diff --git a/Zaptel-to-DAHDI.txt b/Zaptel-to-DAHDI.txt
index 4e92f1c3abc6a8b6500b3029fe8511e7a87c11a8..3693d23492aaf94c61842d77967e1d841e13b5ea 100644
--- a/Zaptel-to-DAHDI.txt
+++ b/Zaptel-to-DAHDI.txt
@@ -16,10 +16,12 @@ renamed; the new names are:
 
   chan_zap.so -> chan_dahdi.so
   app_zapbarge.so -> app_dahdibarge.so
-  app_zapras.so -> app_dahdiras.so
   app_zapscan.so -> app_dahdiscan.so
   codec_zap.so -> codec_dahdi.so
 
+The following modules have been removed:
+  app_zapras.so -> app_dahdiras.so
+
 Second, the behavior of many modules has changed due to the switch to
 DAHDI; the changes are listed below.
 
@@ -46,7 +48,8 @@ app_dahdibarge.so:
 
 app_dahdiras.so:
 
-  The ZapRAS application has been renamed to DAHDIRAS.
+  The ZapRAS application was renamed to DAHDIRAS. This application has
+  since been removed.
 
 app_dahdiscan.so:
 
diff --git a/addons/Makefile b/addons/Makefile
index 866d34b1a5029df9ccb6a635914600adbd940673..06360dbfa54748b7138985769df41ed2a785fa6c 100644
--- a/addons/Makefile
+++ b/addons/Makefile
@@ -28,9 +28,7 @@ H323SOURCE:=$(addprefix ooh323c/src/,$(OOH323C)) ooh323cDriver.c
 
 H323CFLAGS:=-Iooh323c/src -Iooh323c/src/h323
 
-ALL_C_MODS:=app_mysql \
-            cdr_mysql \
-            chan_mobile \
+ALL_C_MODS:=chan_mobile \
             chan_ooh323 \
             format_mp3 \
             res_config_mysql
@@ -64,6 +62,10 @@ chan_ooh323.so: _ASTCFLAGS+=$(H323CFLAGS)
 $(call MOD_ADD_C,chan_ooh323,$(H323SOURCE))
 
 ifneq ($(wildcard mp3/Makefile),)
+# At the current time, the fate of mp3 is in flux so it didn't make sense to
+# add configure/makeopts processing for array-bounds since this is the only
+# source file that needs that warning suppressed.
+mp3/layer3.o: _ASTCFLAGS+=-Wno-array-bounds
 $(call MOD_ADD_C,format_mp3,mp3/common.c mp3/dct64_i386.c mp3/decode_ntom.c mp3/layer3.c mp3/tabinit.c mp3/interface.c)
 
 .PHONY: check_mp3
diff --git a/addons/app_mysql.c b/addons/app_mysql.c
deleted file mode 100644
index 6e35a7c5fafb0dea7e051f0f6e2b438100589a19..0000000000000000000000000000000000000000
--- a/addons/app_mysql.c
+++ /dev/null
@@ -1,669 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2004, Constantine Filin and Christos Ricudis
- *
- * Christos Ricudis <ricudis@itc.auth.gr>
- * Constantine Filin <cf@intermedia.net>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*!
- * \file
- * \brief MYSQL dialplan application
- * \ingroup applications
- */
-
-/*! \li \ref app_mysql.c uses the configuration file \ref app_mysql.conf
- * \addtogroup configuration_file Configuration Files
- */
-
-/*!
- * \page app_mysql.conf app_mysql.conf
- * \verbinclude app_mysql.conf.sample
- */
-
-/*** MODULEINFO
-	<depend>mysqlclient</depend>
-	<defaultenabled>no</defaultenabled>
-	<support_level>deprecated</support_level>
-	<replacement>func_odbc</replacement>
-	<deprecated_in>1.8</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include "asterisk.h"
-
-#include <mysql/mysql.h>
-
-#include "asterisk/file.h"
-#include "asterisk/logger.h"
-#include "asterisk/channel.h"
-#include "asterisk/pbx.h"
-#include "asterisk/module.h"
-#include "asterisk/linkedlists.h"
-#include "asterisk/chanvars.h"
-#include "asterisk/lock.h"
-#include "asterisk/options.h"
-#include "asterisk/app.h"
-#include "asterisk/config.h"
-
-#define EXTRA_LOG 0
-
-enum { NULLSTRING, NULLVALUE, EMPTYSTRING } nullvalue = NULLSTRING;
-
-static const char app[] = "MYSQL";
-
-static const char synopsis[] = "Do several mySQLy things";
-
-static const char descrip[] =
-"MYSQL():  Do several mySQLy things\n"
-"Syntax:\n"
-"  MYSQL(Set timeout <num>)\n"
-"    Set the connection timeout, in seconds.\n"
-"  MYSQL(Connect connid dhhost[:dbport] dbuser dbpass dbname [dbcharset])\n"
-"    Connects to a database.  Arguments contain standard MySQL parameters\n"
-"    passed to function mysql_real_connect.  Optional parameter dbcharset\n"
-"    defaults to 'latin1'.  Connection identifer returned in ${connid}\n"
-"  MYSQL(Query resultid ${connid} query-string)\n"
-"    Executes standard MySQL query contained in query-string using established\n"
-"    connection identified by ${connid}. Result of query is stored in ${resultid}.\n"
-"  MYSQL(Nextresult resultid ${connid}\n"
-"    If last query returned more than one result set, it stores the next\n"
-"    result set in ${resultid}. It's useful with stored procedures\n"
-"  MYSQL(Fetch fetchid ${resultid} var1 var2 ... varN)\n"
-"    Fetches a single row from a result set contained in ${result_identifier}.\n"
-"    Assigns returned fields to ${var1} ... ${varn}.  ${fetchid} is set TRUE\n"
-"    if additional rows exist in result set.\n"
-"  MYSQL(Clear ${resultid})\n"
-"    Frees memory and datastructures associated with result set.\n"
-"  MYSQL(Disconnect ${connid})\n"
-"    Disconnects from named connection to MySQL.\n"
-"  On exit, always returns 0. Sets MYSQL_STATUS to 0 on success and -1 on error.\n";
-
-/*
-EXAMPLES OF USE :
-
-exten => s,2,MYSQL(Connect connid localhost asterisk mypass credit utf8)
-exten => s,3,MYSQL(Query resultid ${connid} SELECT username,credit FROM credit WHERE callerid=${CALLERIDNUM})
-exten => s,4,MYSQL(Fetch fetchid ${resultid} datavar1 datavar2)
-exten => s,5,GotoIf(${fetchid}?6:8)
-exten => s,6,Festival("User ${datavar1} currently has credit balance of ${datavar2} dollars.")
-exten => s,7,Goto(s,4)
-exten => s,8,MYSQL(Clear ${resultid})
-exten => s,9,MYSQL(Disconnect ${connid})
-*/
-
-AST_MUTEX_DEFINE_STATIC(_mysql_mutex);
-
-#define MYSQL_CONFIG "app_mysql.conf"
-#define MYSQL_CONFIG_OLD "mysql.conf"
-#define AST_MYSQL_ID_DUMMY   0
-#define AST_MYSQL_ID_CONNID  1
-#define AST_MYSQL_ID_RESID   2
-#define AST_MYSQL_ID_FETCHID 3
-
-static int autoclear = 0;
-
-static void mysql_ds_destroy(void *data);
-static void mysql_ds_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan);
-
-static const struct ast_datastore_info mysql_ds_info = {
-	.type = "APP_ADDON_SQL_MYSQL",
-	.destroy = mysql_ds_destroy,
-	.chan_fixup = mysql_ds_fixup,
-};
-
-struct ast_MYSQL_id {
-	struct ast_channel *owner;
-	int identifier_type; /* 0=dummy, 1=connid, 2=resultid */
-	int identifier;
-	void *data;
-	AST_LIST_ENTRY(ast_MYSQL_id) entries;
-} *ast_MYSQL_id;
-
-AST_LIST_HEAD(MYSQLidshead,ast_MYSQL_id) _mysql_ids_head;
-
-static void mysql_ds_destroy(void *data)
-{
-	/* Destroy any IDs owned by the channel */
-	struct ast_MYSQL_id *i;
-	if (AST_LIST_LOCK(&_mysql_ids_head)) {
-		ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
-	} else {
-		AST_LIST_TRAVERSE_SAFE_BEGIN(&_mysql_ids_head, i, entries) {
-			if (i->owner == data) {
-				AST_LIST_REMOVE_CURRENT(entries);
-				if (i->identifier_type == AST_MYSQL_ID_CONNID) {
-					/* Drop connection */
-					mysql_close(i->data);
-				} else if (i->identifier_type == AST_MYSQL_ID_RESID) {
-					/* Drop result */
-					mysql_free_result(i->data);
-				}
-				ast_free(i);
-			}
-		}
-		AST_LIST_TRAVERSE_SAFE_END
-		AST_LIST_UNLOCK(&_mysql_ids_head);
-	}
-}
-
-static void mysql_ds_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan)
-{
-	/* Destroy any IDs owned by the channel */
-	struct ast_MYSQL_id *i;
-	if (AST_LIST_LOCK(&_mysql_ids_head)) {
-		ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
-	} else {
-		AST_LIST_TRAVERSE_SAFE_BEGIN(&_mysql_ids_head, i, entries) {
-			if (i->owner == data) {
-				AST_LIST_REMOVE_CURRENT(entries);
-				if (i->identifier_type == AST_MYSQL_ID_CONNID) {
-					/* Drop connection */
-					mysql_close(i->data);
-				} else if (i->identifier_type == AST_MYSQL_ID_RESID) {
-					/* Drop result */
-					mysql_free_result(i->data);
-				}
-				ast_free(i);
-			}
-		}
-		AST_LIST_TRAVERSE_SAFE_END
-		AST_LIST_UNLOCK(&_mysql_ids_head);
-	}
-}
-
-/* helpful procs */
-static void *find_identifier(int identifier, int identifier_type)
-{
-	struct MYSQLidshead *headp = &_mysql_ids_head;
-	struct ast_MYSQL_id *i;
-	void *res=NULL;
-	int found=0;
-
-	if (AST_LIST_LOCK(headp)) {
-		ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
-	} else {
-		AST_LIST_TRAVERSE(headp, i, entries) {
-			if ((i->identifier == identifier) && (i->identifier_type == identifier_type)) {
-				found = 1;
-				res = i->data;
-				break;
-			}
-		}
-		if (!found) {
-			ast_log(LOG_WARNING, "Identifier %d, identifier_type %d not found in identifier list\n", identifier, identifier_type);
-		}
-		AST_LIST_UNLOCK(headp);
-	}
-
-	return res;
-}
-
-static int add_identifier(struct ast_channel *chan, int identifier_type, void *data)
-{
-	struct ast_MYSQL_id *i = NULL, *j = NULL;
-	struct MYSQLidshead *headp = &_mysql_ids_head;
-	int maxidentifier = 0;
-
-	if (AST_LIST_LOCK(headp)) {
-		ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
-		return -1;
-	} else {
-		i = ast_malloc(sizeof(*i));
-		AST_LIST_TRAVERSE(headp, j, entries) {
-			if (j->identifier > maxidentifier) {
-				maxidentifier = j->identifier;
-			}
-		}
-		i->identifier = maxidentifier + 1;
-		i->identifier_type = identifier_type;
-		i->data = data;
-		i->owner = chan;
-		AST_LIST_INSERT_HEAD(headp, i, entries);
-		AST_LIST_UNLOCK(headp);
-	}
-	return i->identifier;
-}
-
-static int del_identifier(int identifier, int identifier_type)
-{
-	struct ast_MYSQL_id *i;
-	struct MYSQLidshead *headp = &_mysql_ids_head;
-	int found = 0;
-
-	if (AST_LIST_LOCK(headp)) {
-		ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
-	} else {
-		AST_LIST_TRAVERSE(headp, i, entries) {
-			if ((i->identifier == identifier) &&
-			    (i->identifier_type == identifier_type)) {
-				AST_LIST_REMOVE(headp, i, entries);
-				ast_free(i);
-				found = 1;
-				break;
-			}
-		}
-		AST_LIST_UNLOCK(headp);
-	}
-
-	if (found == 0) {
-		ast_log(LOG_WARNING, "Could not find identifier %d, identifier_type %d in list to delete\n", identifier, identifier_type);
-		return -1;
-	} else {
-		return 0;
-	}
-}
-
-static int set_asterisk_int(struct ast_channel *chan, char *varname, int id)
-{
-	if (id >= 0) {
-		char s[12] = "";
-		snprintf(s, sizeof(s), "%d", id);
-		ast_debug(5, "MYSQL: setting var '%s' to value '%s'\n", varname, s);
-		pbx_builtin_setvar_helper(chan, varname, s);
-	}
-	return id;
-}
-
-static int add_identifier_and_set_asterisk_int(struct ast_channel *chan, char *varname, int identifier_type, void *data)
-{
-	return set_asterisk_int(chan, varname, add_identifier(chan, identifier_type, data));
-}
-
-static int safe_scan_int(char **data, char *delim, int def)
-{
-	char *end;
-	int res = def;
-	char *s = strsep(data, delim);
-	if (s) {
-		res = strtol(s, &end, 10);
-		if (*end)
-			res = def;  /* not an integer */
-	}
-	return res;
-}
-
-static int aMYSQL_set(struct ast_channel *chan, const char *data)
-{
-	char *var, *tmp, *parse;
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(set);
-		AST_APP_ARG(variable);
-		AST_APP_ARG(value);
-	);
-
-	parse = ast_strdupa(data);
-	AST_NONSTANDARD_APP_ARGS(args, parse, ' ');
-
-	if (args.argc == 3) {
-		var = ast_alloca(6 + strlen(args.variable) + 1);
-		sprintf(var, "MYSQL_%s", args.variable);
-
-		/* Make the parameter case-insensitive */
-		for (tmp = var + 6; *tmp; tmp++)
-			*tmp = toupper(*tmp);
-
-		pbx_builtin_setvar_helper(chan, var, args.value);
-	}
-	return 0;
-}
-
-/* MYSQL operations */
-static int aMYSQL_connect(struct ast_channel *chan, const char *data)
-{
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(connect);
-		AST_APP_ARG(connid);
-		AST_APP_ARG(dbhost);
-		AST_APP_ARG(dbuser);
-		AST_APP_ARG(dbpass);
-		AST_APP_ARG(dbname);
-		AST_APP_ARG(dbcharset);
-	);
-	MYSQL *mysql;
-	int timeout;
-	const char *ctimeout;
-	unsigned int port = 0;
-	char *port_str;
-	char *parse = ast_strdupa(data);
-
-	AST_NONSTANDARD_APP_ARGS(args, parse, ' ');
-
-	if (args.argc < 6) {
-		ast_log(LOG_WARNING, "MYSQL_connect is missing some arguments\n");
-		return -1;
-	}
-
-	if (!(mysql = mysql_init(NULL))) {
-		ast_log(LOG_WARNING, "mysql_init returned NULL\n");
-		return -1;
-	}
-
-	ctimeout = pbx_builtin_getvar_helper(chan, "MYSQL_TIMEOUT");
-	if (ctimeout && sscanf(ctimeout, "%30d", &timeout) == 1) {
-		mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&timeout);
-	}
-	if(args.dbcharset && strlen(args.dbcharset) > 2){
-		char set_names[255];
-		char statement[512];
-		snprintf(set_names, sizeof(set_names), "SET NAMES %s", args.dbcharset);
-		mysql_real_escape_string(mysql, statement, set_names, sizeof(set_names));
-		mysql_options(mysql, MYSQL_INIT_COMMAND, set_names);
-		mysql_options(mysql, MYSQL_SET_CHARSET_NAME, args.dbcharset);
-	}
-
-	if ((port_str = strchr(args.dbhost, ':'))) {
-		*port_str++ = '\0';
-		if (sscanf(port_str, "%u", &port) != 1) {
-			ast_log(LOG_WARNING, "Invalid port: '%s'\n", port_str);
-			port = 0;
-		}
-	}
-
-	if (!mysql_real_connect(mysql, args.dbhost, args.dbuser, args.dbpass, args.dbname, port, NULL,
-#ifdef CLIENT_MULTI_STATEMENTS
-			CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS
-#elif defined(CLIENT_MULTI_QUERIES)
-			CLIENT_MULTI_QUERIES
-#else
-			0
-#endif
-		)) {
-		ast_log(LOG_WARNING, "mysql_real_connect(mysql,%s,%s,dbpass,%s,...) failed(%d): %s\n",
-				args.dbhost, args.dbuser, args.dbname, mysql_errno(mysql), mysql_error(mysql));
-		return -1;
-	}
-
-	add_identifier_and_set_asterisk_int(chan, args.connid, AST_MYSQL_ID_CONNID, mysql);
-	return 0;
-}
-
-static int aMYSQL_query(struct ast_channel *chan, const char *data)
-{
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(query);
-		AST_APP_ARG(resultid);
-		AST_APP_ARG(connid);
-		AST_APP_ARG(sql);
-	);
-	MYSQL       *mysql;
-	MYSQL_RES   *mysqlres;
-	int connid;
-	int mysql_query_res;
-	char *parse = ast_strdupa(data);
-
-	AST_NONSTANDARD_APP_ARGS(args, parse, ' ');
-
-	if (args.argc != 4 || (connid = atoi(args.connid)) == 0) {
-		ast_log(LOG_WARNING, "missing some arguments\n");
-		return -1;
-	}
-
-	if (!(mysql = find_identifier(connid, AST_MYSQL_ID_CONNID))) {
-		ast_log(LOG_WARNING, "Invalid connection identifier %s passed in aMYSQL_query\n", args.connid);
-		return -1;
-	}
-
-	if ((mysql_query_res = mysql_query(mysql, args.sql)) != 0) {
-		ast_log(LOG_WARNING, "aMYSQL_query: mysql_query failed. Error: %s\n", mysql_error(mysql));
-		return -1;
-	}
-
-	if ((mysqlres = mysql_store_result(mysql))) {
-		add_identifier_and_set_asterisk_int(chan, args.resultid, AST_MYSQL_ID_RESID, mysqlres);
-		return 0;
-	} else if (!mysql_field_count(mysql)) {
-		return 0;
-	} else
-		ast_log(LOG_WARNING, "mysql_store_result() failed on query %s\n", args.sql);
-
-	return -1;
-}
-
-static int aMYSQL_nextresult(struct ast_channel *chan, const char *data)
-{
-	MYSQL       *mysql;
-	MYSQL_RES   *mysqlres;
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(nextresult);
-		AST_APP_ARG(resultid);
-		AST_APP_ARG(connid);
-	);
-	int connid = -1;
-	char *parse = ast_strdupa(data);
-
-	AST_NONSTANDARD_APP_ARGS(args, parse, ' ');
-	sscanf(args.connid, "%30d", &connid);
-
-	if (args.argc != 3 || connid <= 0) {
-		ast_log(LOG_WARNING, "missing some arguments\n");
-		return -1;
-	}
-
-	if (!(mysql = find_identifier(connid, AST_MYSQL_ID_CONNID))) {
-		ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aMYSQL_query\n", connid);
-		return -1;
-	}
-
-	if (mysql_more_results(mysql)) {
-		mysql_next_result(mysql);
-		if ((mysqlres = mysql_store_result(mysql))) {
-			add_identifier_and_set_asterisk_int(chan, args.resultid, AST_MYSQL_ID_RESID, mysqlres);
-			return 0;
-		} else if (!mysql_field_count(mysql)) {
-			return 0;
-		} else
-			ast_log(LOG_WARNING, "mysql_store_result() failed on storing next_result\n");
-	} else
-		ast_log(LOG_WARNING, "mysql_more_results() result set has no more results\n");
-
-	return 0;
-}
-
-
-static int aMYSQL_fetch(struct ast_channel *chan, const char *data)
-{
-	MYSQL_RES *mysqlres;
-	MYSQL_ROW mysqlrow;
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(fetch);
-		AST_APP_ARG(resultvar);
-		AST_APP_ARG(fetchid);
-		AST_APP_ARG(vars);
-	);
-	char *s5, *parse;
-	int resultid = -1, numFields, j;
-
-	parse = ast_strdupa(data);
-	AST_NONSTANDARD_APP_ARGS(args, parse, ' ');
-	sscanf(args.fetchid, "%30d", &resultid);
-
-	if (args.resultvar && (resultid >= 0) ) {
-		if ((mysqlres = find_identifier(resultid, AST_MYSQL_ID_RESID)) != NULL) {
-			/* Grab the next row */
-			if ((mysqlrow = mysql_fetch_row(mysqlres)) != NULL) {
-				numFields = mysql_num_fields(mysqlres);
-				for (j = 0; j < numFields; j++) {
-					s5 = strsep(&args.vars, " ");
-					if (s5 == NULL) {
-						ast_log(LOG_WARNING, "ast_MYSQL_fetch: More fields (%d) than variables (%d)\n", numFields, j);
-						break;
-					}
-
-					pbx_builtin_setvar_helper(chan, s5, mysqlrow[j] ? mysqlrow[j] :
-						nullvalue == NULLSTRING ? "NULL" :
-						nullvalue == EMPTYSTRING ? "" :
-						NULL);
-				}
-				ast_debug(5, "ast_MYSQL_fetch: numFields=%d\n", numFields);
-				set_asterisk_int(chan, args.resultvar, 1); /* try more rows */
-			} else {
-				ast_debug(5, "ast_MYSQL_fetch : EOF\n");
-				set_asterisk_int(chan, args.resultvar, 0); /* no more rows */
-			}
-			return 0;
-		} else {
-			set_asterisk_int(chan, args.resultvar, 0);
-			ast_log(LOG_WARNING, "aMYSQL_fetch: Invalid result identifier %d passed\n", resultid);
-		}
-	} else {
-		ast_log(LOG_WARNING, "aMYSQL_fetch: missing some arguments\n");
-	}
-
-	return -1;
-}
-
-static int aMYSQL_clear(struct ast_channel *chan, const char *data)
-{
-	MYSQL_RES *mysqlres;
-
-	int id;
-	char *parse = ast_strdupa(data);
-	strsep(&parse, " "); /* eat the first token, we already know it :P */
-	id = safe_scan_int(&parse, " \n", -1);
-	if ((mysqlres = find_identifier(id, AST_MYSQL_ID_RESID)) == NULL) {
-		ast_log(LOG_WARNING, "Invalid result identifier %d passed in aMYSQL_clear\n", id);
-	} else {
-		mysql_free_result(mysqlres);
-		del_identifier(id, AST_MYSQL_ID_RESID);
-	}
-
-	return 0;
-}
-
-static int aMYSQL_disconnect(struct ast_channel *chan, const char *data)
-{
-	MYSQL *mysql;
-	int id;
-	char *parse = ast_strdupa(data);
-	strsep(&parse, " "); /* eat the first token, we already know it :P */
-
-	id = safe_scan_int(&parse, " \n", -1);
-	if ((mysql = find_identifier(id, AST_MYSQL_ID_CONNID)) == NULL) {
-		ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aMYSQL_disconnect\n", id);
-	} else {
-		mysql_close(mysql);
-		del_identifier(id, AST_MYSQL_ID_CONNID);
-	}
-
-	return 0;
-}
-
-static int MYSQL_exec(struct ast_channel *chan, const char *data)
-{
-	int result;
-	char sresult[10];
-
-	ast_debug(5, "MYSQL: data=%s\n", data);
-
-	if (!data) {
-		ast_log(LOG_WARNING, "MYSQL requires an argument (see manual)\n");
-		return -1;
-	}
-
-	result = 0;
-
-	if (autoclear) {
-		struct ast_datastore *mysql_store = NULL;
-
-		ast_channel_lock(chan);
-		mysql_store = ast_channel_datastore_find(chan, &mysql_ds_info, NULL);
-		if (!mysql_store) {
-			if (!(mysql_store = ast_datastore_alloc(&mysql_ds_info, NULL))) {
-				ast_log(LOG_WARNING, "Unable to allocate new datastore.\n");
-			} else {
-				mysql_store->data = chan;
-				ast_channel_datastore_add(chan, mysql_store);
-			}
-		}
-		ast_channel_unlock(chan);
-	}
-	ast_mutex_lock(&_mysql_mutex);
-
-	if (strncasecmp("connect", data, strlen("connect")) == 0) {
-		result = aMYSQL_connect(chan, data);
-	} else if (strncasecmp("query", data, strlen("query")) == 0) {
-		result = aMYSQL_query(chan, data);
-	} else if (strncasecmp("nextresult", data, strlen("nextresult")) == 0) {
-		result = aMYSQL_nextresult(chan, data);
-	} else if (strncasecmp("fetch", data, strlen("fetch")) == 0) {
-		result = aMYSQL_fetch(chan, data);
-	} else if (strncasecmp("clear", data, strlen("clear")) == 0) {
-		result = aMYSQL_clear(chan, data);
-	} else if (strncasecmp("disconnect", data, strlen("disconnect")) == 0) {
-		result = aMYSQL_disconnect(chan, data);
-	} else if (strncasecmp("set", data, 3) == 0) {
-		result = aMYSQL_set(chan, data);
-	} else {
-		ast_log(LOG_WARNING, "Unknown argument to MYSQL application : %s\n", data);
-		result = -1;
-	}
-
-	ast_mutex_unlock(&_mysql_mutex);
-
-	snprintf(sresult, sizeof(sresult), "%d", result);
-	pbx_builtin_setvar_helper(chan, "MYSQL_STATUS", sresult);
-	return 0;
-}
-
-static int unload_module(void)
-{
-	return ast_unregister_application(app);
-}
-
-/*!
- * \brief Load the module
- *
- * Module loading including tests for configuration or dependencies.
- * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
- * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
- * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the
- * configuration file or other non-critical problem return
- * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
- */
-static int load_module(void)
-{
-	struct MYSQLidshead *headp = &_mysql_ids_head;
-	struct ast_flags config_flags = { 0 };
-	struct ast_config *cfg = ast_config_load(MYSQL_CONFIG, config_flags);
-	const char *temp;
-
-	if (!cfg) {
-		/* Backwards compatibility ftw */
-		cfg = ast_config_load(MYSQL_CONFIG_OLD, config_flags);
-	}
-
-	if (cfg) {
-		if ((temp = ast_variable_retrieve(cfg, "general", "nullvalue"))) {
-			if (!strcasecmp(temp, "nullstring")) {
-				nullvalue = NULLSTRING;
-			} else if (!strcasecmp(temp, "emptystring")) {
-				nullvalue = EMPTYSTRING;
-			} else if (!strcasecmp(temp, "null")) {
-				nullvalue = NULLVALUE;
-			} else {
-				ast_log(LOG_WARNING, "Illegal value for 'nullvalue': '%s' (must be 'nullstring', 'null', or 'emptystring')\n", temp);
-			}
-		}
-		if ((temp = ast_variable_retrieve(cfg, "general", "autoclear")) && ast_true(temp)) {
-			autoclear = 1;
-		}
-		ast_config_destroy(cfg);
-	}
-
-	AST_LIST_HEAD_INIT(headp);
-	return ast_register_application(app, MYSQL_exec, synopsis, descrip);
-}
-
-AST_MODULE_INFO_STANDARD_DEPRECATED(ASTERISK_GPL_KEY, "Simple Mysql Interface");
diff --git a/addons/cdr_mysql.c b/addons/cdr_mysql.c
deleted file mode 100644
index 25f8762c6131a964b1298d678b495d09912ebc96..0000000000000000000000000000000000000000
--- a/addons/cdr_mysql.c
+++ /dev/null
@@ -1,760 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * James Sharp <jsharp@psychoses.org>
- *
- * Modified August 2003
- * Tilghman Lesher <asterisk__cdr__cdr_mysql__200308@the-tilghman.com>
- *
- * Modified August 6, 2005
- * Joseph Benden <joe@thrallingpenguin.com>
- * Added mysql connection timeout parameter
- * Added an automatic reconnect as to not lose a cdr record
- * Cleaned up the original code to match the coding guidelines
- *
- * Modified Juli 2006
- * Martin Portmann <map@infinitum.ch>
- * Added mysql ssl support
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*!
- * \file
- * \brief MySQL CDR backend
- * \ingroup cdr_drivers
- */
-
-/*** MODULEINFO
-	<depend>mysqlclient</depend>
-	<defaultenabled>no</defaultenabled>
-	<support_level>deprecated</support_level>
-	<replacement>cdr_adaptive_odbc</replacement>
-	<deprecated_in>1.8</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include "asterisk.h"
-
-#include <mysql/mysql.h>
-#include <mysql/errmsg.h>
-
-#include "asterisk/config.h"
-#include "asterisk/options.h"
-#include "asterisk/channel.h"
-#include "asterisk/cdr.h"
-#include "asterisk/module.h"
-#include "asterisk/logger.h"
-#include "asterisk/cli.h"
-#include "asterisk/strings.h"
-#include "asterisk/linkedlists.h"
-#include "asterisk/threadstorage.h"
-
-#define DATE_FORMAT "%Y-%m-%d %T"
-
-#ifndef MYSQL_PORT
-# ifdef MARIADB_PORT
-#  define MYSQL_PORT MARIADB_PORT
-# else
-#  define MYSQL_PORT 3306
-# endif
-#endif
-
-AST_THREADSTORAGE(sql1_buf);
-AST_THREADSTORAGE(sql2_buf);
-AST_THREADSTORAGE(escape_buf);
-
-static const char desc[] = "MySQL CDR Backend";
-static const char name[] = "mysql";
-static const char config[] = "cdr_mysql.conf";
-
-static struct ast_str *hostname = NULL, *dbname = NULL, *dbuser = NULL, *password = NULL, *dbsock = NULL, *dbtable = NULL, *dbcharset = NULL, *cdrzone = NULL;
-
-static struct ast_str *ssl_ca = NULL, *ssl_cert = NULL, *ssl_key = NULL;
-
-static int dbport = 0;
-static int connected = 0;
-static time_t connect_time = 0;
-static int records = 0;
-static int totalrecords = 0;
-static int timeout = 0;
-static int calldate_compat = 0;
-
-AST_MUTEX_DEFINE_STATIC(mysql_lock);
-
-struct unload_string {
-	AST_LIST_ENTRY(unload_string) entry;
-	struct ast_str *str;
-};
-
-static AST_LIST_HEAD_STATIC(unload_strings, unload_string);
-
-struct column {
-	char *name;
-	char *cdrname;
-	char *staticvalue;
-	char *type;
-	AST_LIST_ENTRY(column) list;
-};
-
-/* Protected with mysql_lock */
-static AST_RWLIST_HEAD_STATIC(columns, column);
-
-static MYSQL mysql = { { NULL }, };
-
-static char *handle_cli_cdr_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "cdr mysql status";
-		e->usage =
-			"Usage: cdr mysql status\n"
-			"       Shows current connection status for cdr_mysql\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 3)
-		return CLI_SHOWUSAGE;
-
-	if (connected) {
-		char status[256];
-		char status2[100] = "";
-		char buf[362]; /* 256+100+" for "+NULL */
-		int ctime = time(NULL) - connect_time;
-		if (dbport)
-			snprintf(status, 255, "Connected to %s@%s, port %d", ast_str_buffer(dbname), ast_str_buffer(hostname), dbport);
-		else if (dbsock)
-			snprintf(status, 255, "Connected to %s on socket file %s", ast_str_buffer(dbname), S_OR(ast_str_buffer(dbsock), "default"));
-		else
-			snprintf(status, 255, "Connected to %s@%s", ast_str_buffer(dbname), ast_str_buffer(hostname));
-
-		if (ast_str_strlen(dbuser))
-			snprintf(status2, 99, " with username %s", ast_str_buffer(dbuser));
-		if (ast_str_strlen(dbtable))
-			snprintf(status2, 99, " using table %s", ast_str_buffer(dbtable));
-
-		snprintf(buf, sizeof(buf), "%s%s for ", status, status2);
-		ast_cli_print_timestr_fromseconds(a->fd, ctime, buf);
-
-		if (records == totalrecords)
-			ast_cli(a->fd, "  Wrote %d records since last restart.\n", totalrecords);
-		else
-			ast_cli(a->fd, "  Wrote %d records since last restart and %d records since last reconnect.\n", totalrecords, records);
-	} else {
-		ast_cli(a->fd, "Not currently connected to a MySQL server.\n");
-	}
-
-	return CLI_SUCCESS;
-}
-
-static struct ast_cli_entry cdr_mysql_status_cli[] = {
-	AST_CLI_DEFINE(handle_cli_cdr_mysql_status, "Show connection status of cdr_mysql"),
-};
-
-static void configure_connection_charset(void)
-{
-	if (ast_str_strlen(dbcharset)) {
-		const char *charset = ast_str_buffer(dbcharset);
-		if (mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, charset)) {
-			ast_log(LOG_WARNING, "Failed to set connection charset. Data inserted might be invalid.\n");
-		}
-	}
-}
-
-static int mysql_log(struct ast_cdr *cdr)
-{
-	struct ast_str *sql1 = ast_str_thread_get(&sql1_buf, 1024), *sql2 = ast_str_thread_get(&sql2_buf, 1024);
-	int retries = 5;
-#ifdef HAVE_MYSQLCLIENT_BOOL
-	bool my_bool_true = 1;
-#elif HAVE_MYSQLCLIENT_MY_BOOL
-	my_bool my_bool_true = 1;
-#endif
-
-	if (!sql1 || !sql2) {
-		ast_log(LOG_ERROR, "Memory error\n");
-		return -1;
-	}
-
-	ast_mutex_lock(&mysql_lock);
-
-db_reconnect:
-	if ((!connected) && (hostname || dbsock) && dbuser && password && dbname && dbtable ) {
-		/* Attempt to connect */
-		mysql_init(&mysql);
-		/* Add option to quickly timeout the connection */
-		if (timeout && mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *)&timeout) != 0) {
-			ast_log(LOG_ERROR, "mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
-		}
-#if MYSQL_VERSION_ID >= 50013
-		/* Add option for automatic reconnection */
-		if (mysql_options(&mysql, MYSQL_OPT_RECONNECT, &my_bool_true) != 0) {
-			ast_log(LOG_ERROR, "mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
-		}
-#endif
-		if (ssl_ca || ssl_cert || ssl_key) {
-			mysql_ssl_set(&mysql, ssl_key ? ast_str_buffer(ssl_key) : NULL, ssl_cert ? ast_str_buffer(ssl_cert) : NULL, ssl_ca ? ast_str_buffer(ssl_ca) : NULL, NULL, NULL);
-		}
-
-		configure_connection_charset();
-
-		if (mysql_real_connect(&mysql, ast_str_buffer(hostname), ast_str_buffer(dbuser), ast_str_buffer(password), ast_str_buffer(dbname), dbport, dbsock && ast_str_strlen(dbsock) ? ast_str_buffer(dbsock) : NULL, ssl_ca ? CLIENT_SSL : 0)) {
-			connected = 1;
-			connect_time = time(NULL);
-			records = 0;
-		} else {
-			ast_log(LOG_ERROR, "Cannot connect to database server %s: (%d) %s\n", ast_str_buffer(hostname), mysql_errno(&mysql), mysql_error(&mysql));
-			connected = 0;
-		}
-	} else {
-		/* Long connection - ping the server */
-		int error;
-		if ((error = mysql_ping(&mysql))) {
-			connected = 0;
-			records = 0;
-			switch (mysql_errno(&mysql)) {
-				case CR_SERVER_GONE_ERROR:
-				case CR_SERVER_LOST:
-					ast_log(LOG_ERROR, "Server has gone away. Attempting to reconnect.\n");
-					break;
-				default:
-					ast_log(LOG_ERROR, "Unknown connection error: (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
-			}
-			retries--;
-			if (retries) {
-				goto db_reconnect;
-			} else {
-				ast_log(LOG_ERROR, "Retried to connect five times, giving up.\n");
-			}
-		}
-	}
-
-	if (connected) {
-		int column_count = 0;
-		char *cdrname;
-		char workspace[2048], *value = NULL;
-		struct column *entry;
-		struct ast_str *escape = ast_str_thread_get(&escape_buf, 16);
-
-		ast_str_set(&sql1, 0, "INSERT INTO %s (", AS_OR(dbtable, "cdr"));
-		ast_str_set(&sql2, 0, ") VALUES (");
-
-		AST_RWLIST_RDLOCK(&columns);
-		AST_RWLIST_TRAVERSE(&columns, entry, list) {
-			if (!strcmp(entry->name, "calldate")) {
-				/*!\note
-				 * For some dumb reason, "calldate" used to be formulated using
-				 * the datetime the record was posted, rather than the start
-				 * time of the call.  If someone really wants the old compatible
-				 * behavior, it's provided here.
-				 */
-				if (calldate_compat) {
-					struct timeval tv = ast_tvnow();
-					struct ast_tm tm;
-					char timestr[128];
-					ast_localtime(&tv, &tm, ast_str_strlen(cdrzone) ? ast_str_buffer(cdrzone) : NULL);
-					ast_strftime(timestr, sizeof(timestr), DATE_FORMAT, &tm);
-					value = ast_strdupa(timestr);
-					cdrname = "calldate";
-				} else {
-					cdrname = "start";
-				}
-			} else {
-				cdrname = entry->cdrname;
-			}
-
-			/* Construct SQL */
-
-			/* Need the type and value to determine if we want the raw value or not */
-			if (entry->staticvalue) {
-				value = ast_strdupa(entry->staticvalue);
-			} else if ((!strcmp(cdrname, "disposition") ||
-				 !strcmp(cdrname, "amaflags")) &&
-				(strstr(entry->type, "int") ||
-				 strstr(entry->type, "dec") ||
-				 strstr(entry->type, "float") ||
-				 strstr(entry->type, "double") ||
-				 strstr(entry->type, "real") ||
-				 strstr(entry->type, "numeric") ||
-				 strstr(entry->type, "fixed"))) {
-				ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 1);
-			} else if (!strcmp(cdrname, "start") || !strcmp(cdrname, "answer") ||
-				 !strcmp(cdrname, "end")) {
-				struct ast_tm tm;
-				char timestr[128];
-				ast_localtime(&cdr->start, &tm, ast_str_strlen(cdrzone) ? ast_str_buffer(cdrzone) : NULL);
-				ast_strftime(timestr, sizeof(timestr), DATE_FORMAT, &tm);
-				value = ast_strdupa(timestr);
-			} else if (!strcmp(cdrname, "calldate")) {
-				/* Skip calldate - the value has already been dup'd */
-			} else {
-				ast_cdr_format_var(cdr, cdrname, &value, workspace, sizeof(workspace), 0);
-			}
-
-			if (value) {
-				size_t valsz;
-
-				if (column_count++) {
-					ast_str_append(&sql1, 0, ",");
-					ast_str_append(&sql2, 0, ",");
-				}
-
-				if (!strcasecmp(cdrname, "billsec") &&
-					(strstr(entry->type, "float") ||
-					strstr(entry->type, "double") ||
-					strstr(entry->type, "decimal") ||
-					strstr(entry->type, "numeric") ||
-					strstr(entry->type, "real"))) {
-
-					if (!ast_tvzero(cdr->answer)) {
-						snprintf(workspace, sizeof(workspace), "%lf",
-							(double) (ast_tvdiff_us(cdr->end, cdr->answer) / 1000000.0));
-					} else {
-						ast_copy_string(workspace, "0", sizeof(workspace));
-					}
-
-					if (!ast_strlen_zero(workspace)) {
-						value = workspace;
-					}
-				}
-
-				if (!strcasecmp(cdrname, "duration") &&
-					(strstr(entry->type, "float") ||
-					strstr(entry->type, "double") ||
-					strstr(entry->type, "decimal") ||
-					strstr(entry->type, "numeric") ||
-					strstr(entry->type, "real"))) {
-
-					snprintf(workspace, sizeof(workspace), "%lf",
-						(double) (ast_tvdiff_us(cdr->end, cdr->start) / 1000000.0));
-
-					if (!ast_strlen_zero(workspace)) {
-						value = workspace;
-					}
-				}
-
-				ast_str_make_space(&escape, (valsz = strlen(value)) * 2 + 1);
-				mysql_real_escape_string(&mysql, ast_str_buffer(escape), value, valsz);
-
-				ast_str_append(&sql1, 0, "`%s`", entry->name);
-				ast_str_append(&sql2, 0, "'%s'", ast_str_buffer(escape));
-			}
-		}
-		AST_RWLIST_UNLOCK(&columns);
-
-		ast_debug(1, "Inserting a CDR record.\n");
-		ast_str_append(&sql1, 0, "%s)", ast_str_buffer(sql2));
-
-		ast_debug(1, "SQL command as follows: %s\n", ast_str_buffer(sql1));
-
-		if (mysql_real_query(&mysql, ast_str_buffer(sql1), ast_str_strlen(sql1))) {
-			ast_log(LOG_ERROR, "Failed to insert into database: (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
-			mysql_close(&mysql);
-			connected = 0;
-		} else {
-			records++;
-			totalrecords++;
-		}
-	}
-	ast_mutex_unlock(&mysql_lock);
-	return 0;
-}
-
-static void free_strings(void)
-{
-	struct unload_string *us;
-
-	AST_LIST_LOCK(&unload_strings);
-	while ((us = AST_LIST_REMOVE_HEAD(&unload_strings, entry))) {
-		ast_free(us->str);
-		ast_free(us);
-	}
-	AST_LIST_UNLOCK(&unload_strings);
-}
-
-static int my_unload_module(int reload)
-{
-	struct column *entry;
-
-	if (!reload) {
-		if (ast_cdr_unregister(name)) {
-			/* If we can't unregister the backend, we can't unload the module */
-			return -1;
-		}
-	}
-
-	ast_cli_unregister_multiple(cdr_mysql_status_cli, sizeof(cdr_mysql_status_cli) / sizeof(struct ast_cli_entry));
-
-	if (connected) {
-		mysql_close(&mysql);
-		connected = 0;
-		records = 0;
-	}
-
-	free_strings();
-
-	if (!reload) {
-		AST_RWLIST_WRLOCK(&columns);
-	}
-	while ((entry = AST_RWLIST_REMOVE_HEAD(&columns, list))) {
-		ast_free(entry);
-	}
-	if (!reload) {
-		AST_RWLIST_UNLOCK(&columns);
-	}
-
-	dbport = 0;
-	if (reload) {
-		return ast_cdr_backend_suspend(name);
-	} else {
-		/* We unregistered earlier */
-		return 0;
-	}
-}
-
-static int my_load_config_string(struct ast_config *cfg, const char *category, const char *variable, struct ast_str **field, const char *def)
-{
-	struct unload_string *us;
-	const char *tmp;
-
-	if (!(us = ast_calloc(1, sizeof(*us))))
-		return -1;
-
-	if (!(*field = ast_str_create(16))) {
-		ast_free(us);
-		return -1;
-	}
-
-	tmp = ast_variable_retrieve(cfg, category, variable);
-
-	ast_str_set(field, 0, "%s", tmp ? tmp : def);
-
-	us->str = *field;
-
-	AST_LIST_LOCK(&unload_strings);
-	AST_LIST_INSERT_HEAD(&unload_strings, us, entry);
-	AST_LIST_UNLOCK(&unload_strings);
-
-	return 0;
-}
-
-static int my_load_config_number(struct ast_config *cfg, const char *category, const char *variable, int *field, int def)
-{
-	const char *tmp;
-
-	tmp = ast_variable_retrieve(cfg, category, variable);
-
-	if (!tmp || sscanf(tmp, "%30d", field) < 1)
-		*field = def;
-
-	return 0;
-}
-
-/** Connect to MySQL. Initializes the connection.
- *
- * * Assumes the read-write lock for columns is held.
- * * Caller should allocate and free cfg
- * */
-static int my_connect_db(struct ast_config *cfg)
-{
-	struct ast_variable *var;
-	char *temp;
-	MYSQL_ROW row;
-	MYSQL_RES *result;
-	char sqldesc[128];
-#ifdef HAVE_MYSQLCLIENT_BOOL
-	bool my_bool_true = 1;
-#elif HAVE_MYSQLCLIENT_MY_BOOL
-	my_bool my_bool_true = 1;
-#endif
-
-	mysql_init(&mysql);
-
-	if (timeout && mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *)&timeout) != 0) {
-		ast_log(LOG_ERROR, "cdr_mysql: mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
-	}
-
-#if MYSQL_VERSION_ID >= 50013
-	/* Add option for automatic reconnection */
-	if (mysql_options(&mysql, MYSQL_OPT_RECONNECT, &my_bool_true) != 0) {
-		ast_log(LOG_ERROR, "cdr_mysql: mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
-	}
-#endif
-
-	if ((ssl_ca && ast_str_strlen(ssl_ca)) || (ssl_cert && ast_str_strlen(ssl_cert)) || (ssl_key && ast_str_strlen(ssl_key))) {
-		mysql_ssl_set(&mysql,
-			ssl_key ? ast_str_buffer(ssl_key) : NULL,
-			ssl_cert ? ast_str_buffer(ssl_cert) : NULL,
-			ssl_ca ? ast_str_buffer(ssl_ca) : NULL,
-			NULL, NULL);
-	}
-	temp = dbsock && ast_str_strlen(dbsock) ? ast_str_buffer(dbsock) : NULL;
-
-	configure_connection_charset();
-
-	if (!mysql_real_connect(&mysql, ast_str_buffer(hostname), ast_str_buffer(dbuser), ast_str_buffer(password), ast_str_buffer(dbname), dbport, temp, ssl_ca && ast_str_strlen(ssl_ca) ? CLIENT_SSL : 0)) {
-		ast_log(LOG_ERROR, "Failed to connect to mysql database %s on %s.\n", ast_str_buffer(dbname), ast_str_buffer(hostname));
-		connected = 0;
-		records = 0;
-
-		return AST_MODULE_LOAD_SUCCESS;	/* May be reconnected later */
-	}
-
-	ast_debug(1, "Successfully connected to MySQL database.\n");
-	connected = 1;
-	records = 0;
-	connect_time = time(NULL);
-
-	/* Get table description */
-	snprintf(sqldesc, sizeof(sqldesc), "DESC %s", dbtable ? ast_str_buffer(dbtable) : "cdr");
-	if (mysql_query(&mysql, sqldesc)) {
-		ast_log(LOG_ERROR, "Unable to query table description!!  Logging disabled.\n");
-		mysql_close(&mysql);
-		connected = 0;
-
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	if (!(result = mysql_store_result(&mysql))) {
-		ast_log(LOG_ERROR, "Unable to query table description!!  Logging disabled.\n");
-		mysql_close(&mysql);
-		connected = 0;
-
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	while ((row = mysql_fetch_row(result))) {
-		struct column *entry;
-		char *cdrvar = "", *staticvalue = "";
-
-		ast_debug(1, "Got a field '%s' of type '%s'\n", row[0], row[1]);
-		/* Check for an alias or a static value */
-		for (var = ast_variable_browse(cfg, "columns"); var; var = var->next) {
-			if (strncmp(var->name, "alias", 5) == 0 && strcasecmp(var->value, row[0]) == 0 ) {
-				char *alias = ast_strdupa(var->name + 5);
-				cdrvar = ast_strip(alias);
-				ast_verb(3, "Found alias %s for column %s\n", cdrvar, row[0]);
-				break;
-			} else if (strncmp(var->name, "static", 6) == 0 && strcasecmp(var->value, row[0]) == 0) {
-				char *item = ast_strdupa(var->name + 6);
-				item = ast_strip(item);
-				if (item[0] == '"' && item[strlen(item) - 1] == '"') {
-					/* Remove surrounding quotes */
-					item[strlen(item) - 1] = '\0';
-					item++;
-				}
-				staticvalue = item;
-			}
-		}
-
-		entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(row[0]) + 1 + strlen(cdrvar) + 1 + strlen(staticvalue) + 1 + strlen(row[1]) + 1);
-		if (!entry) {
-			ast_log(LOG_ERROR, "Out of memory creating entry for column '%s'\n", row[0]);
-			mysql_free_result(result);
-			return AST_MODULE_LOAD_DECLINE;
-		}
-
-		entry->name = (char *)entry + sizeof(*entry);
-		strcpy(entry->name, row[0]);
-
-		if (!ast_strlen_zero(cdrvar)) {
-			entry->cdrname = entry->name + strlen(row[0]) + 1;
-			strcpy(entry->cdrname, cdrvar);
-		} else { /* Point to same place as the column name */
-			entry->cdrname = (char *)entry + sizeof(*entry);
-		}
-
-		if (!ast_strlen_zero(staticvalue)) {
-			entry->staticvalue = entry->cdrname + strlen(entry->cdrname) + 1;
-			strcpy(entry->staticvalue, staticvalue);
-			ast_debug(1, "staticvalue length: %d\n", (int) strlen(staticvalue) );
-			entry->type = entry->staticvalue + strlen(entry->staticvalue) + 1;
-		} else {
-			entry->type = entry->cdrname + strlen(entry->cdrname) + 1;
-		}
-		strcpy(entry->type, row[1]);
-
-		ast_debug(1, "Entry name '%s'\n", entry->name);
-		ast_debug(1, "   cdrname '%s'\n", entry->cdrname);
-		ast_debug(1, "    static '%s'\n", entry->staticvalue);
-		ast_debug(1, "      type '%s'\n", entry->type);
-
-		AST_LIST_INSERT_TAIL(&columns, entry, list);
-	}
-	mysql_free_result(result);
-
-	return AST_MODULE_LOAD_SUCCESS;
-}
-
-static int my_load_module(int reload)
-{
-	int res;
-	struct ast_config *cfg;
-	struct ast_variable *var;
-	/* CONFIG_STATUS_FILEUNCHANGED is impossible when config_flags is always 0,
-	 * and it has to be zero, so a reload can be sent to tell the driver to
-	 * rescan the table layout. */
-	struct ast_flags config_flags = { 0 };
-	struct column *entry;
-	struct ast_str *compat;
-
-	/* Cannot use a conditionally different flag, because the table layout may
-	 * have changed, which is not detectable by config file change detection,
-	 * but should still cause the configuration to be re-parsed. */
-	cfg = ast_config_load(config, config_flags);
-	if (cfg == CONFIG_STATUS_FILEMISSING) {
-		ast_log(LOG_WARNING, "Unable to load config for mysql CDR's: %s\n", config);
-		return AST_MODULE_LOAD_SUCCESS;
-	} else if (cfg == CONFIG_STATUS_FILEINVALID) {
-		ast_log(LOG_ERROR, "Unable to load configuration file '%s'\n", config);
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	if (reload) {
-		AST_RWLIST_WRLOCK(&columns);
-		my_unload_module(1);
-	}
-
-	var = ast_variable_browse(cfg, "global");
-	if (!var) {
-		/* nothing configured */
-		if (reload) {
-			AST_RWLIST_UNLOCK(&columns);
-		}
-		ast_config_destroy(cfg);
-		return AST_MODULE_LOAD_SUCCESS;
-	}
-
-	res = 0;
-
-	res |= my_load_config_string(cfg, "global", "hostname", &hostname, "localhost");
-	res |= my_load_config_string(cfg, "global", "dbname", &dbname, "astriskcdrdb");
-	res |= my_load_config_string(cfg, "global", "user", &dbuser, "root");
-	res |= my_load_config_string(cfg, "global", "sock", &dbsock, "");
-	res |= my_load_config_string(cfg, "global", "table", &dbtable, "cdr");
-	res |= my_load_config_string(cfg, "global", "password", &password, "");
-
-	res |= my_load_config_string(cfg, "global", "charset", &dbcharset, "");
-
-	res |= my_load_config_string(cfg, "global", "ssl_ca", &ssl_ca, "");
-	res |= my_load_config_string(cfg, "global", "ssl_cert", &ssl_cert, "");
-	res |= my_load_config_string(cfg, "global", "ssl_key", &ssl_key, "");
-
-	res |= my_load_config_number(cfg, "global", "port", &dbport, MYSQL_PORT);
-	res |= my_load_config_number(cfg, "global", "timeout", &timeout, 0);
-	res |= my_load_config_string(cfg, "global", "compat", &compat, "no");
-	res |= my_load_config_string(cfg, "global", "cdrzone", &cdrzone, "");
-	if (ast_str_strlen(cdrzone) == 0) {
-		for (; var; var = var->next) {
-			if (!strcasecmp(var->name, "usegmtime") && ast_true(var->value)) {
-				ast_str_set(&cdrzone, 0, "UTC");
-			}
-		}
-	}
-
-	if (ast_true(ast_str_buffer(compat))) {
-		calldate_compat = 1;
-	} else {
-		calldate_compat = 0;
-	}
-
-	if (res < 0) {
-		if (reload) {
-			AST_RWLIST_UNLOCK(&columns);
-		}
-		ast_config_destroy(cfg);
-		free_strings();
-
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	/* Check for any aliases */
-	if (!reload) {
-		/* Lock, if not already */
-		AST_RWLIST_WRLOCK(&columns);
-	}
-	while ((entry = AST_LIST_REMOVE_HEAD(&columns, list))) {
-		ast_free(entry);
-	}
-
-	ast_debug(1, "Got hostname of %s\n", ast_str_buffer(hostname));
-	ast_debug(1, "Got port of %d\n", dbport);
-	ast_debug(1, "Got a timeout of %d\n", timeout);
-	if (ast_str_strlen(dbsock)) {
-		ast_debug(1, "Got sock file of %s\n", ast_str_buffer(dbsock));
-	}
-	ast_debug(1, "Got user of %s\n", ast_str_buffer(dbuser));
-	ast_debug(1, "Got dbname of %s\n", ast_str_buffer(dbname));
-	ast_debug(1, "Got password of %s\n", ast_str_buffer(password));
-	ast_debug(1, "%sunning in calldate compatibility mode\n", calldate_compat ? "R" : "Not r");
-	ast_debug(1, "Dates and times are localized to %s\n", S_OR(ast_str_buffer(cdrzone), "local timezone"));
-
-	if (ast_str_strlen(dbcharset)) {
-		ast_debug(1, "Got DB charset of %s\n", ast_str_buffer(dbcharset));
-	}
-
-	res = my_connect_db(cfg);
-	AST_RWLIST_UNLOCK(&columns);
-	ast_config_destroy(cfg);
-	if (res != AST_MODULE_LOAD_SUCCESS) {
-		my_unload_module(0);
-		return res;
-	}
-
-	if (!reload) {
-		res = ast_cdr_register(name, desc, mysql_log);
-	} else {
-		res = ast_cdr_backend_unsuspend(name);
-	}
-	if (res) {
-		ast_log(LOG_ERROR, "Unable to register MySQL CDR handling\n");
-	} else {
-		res = ast_cli_register_multiple(cdr_mysql_status_cli, sizeof(cdr_mysql_status_cli) / sizeof(struct ast_cli_entry));
-	}
-
-	if (res) {
-		my_unload_module(0);
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	return AST_MODULE_LOAD_SUCCESS;
-}
-
-static int load_module(void)
-{
-	return my_load_module(0);
-}
-
-static int unload_module(void)
-{
-	return my_unload_module(0);
-}
-
-static int reload(void)
-{
-	int ret;
-
-	ast_mutex_lock(&mysql_lock);
-	ret = my_load_module(1);
-	ast_mutex_unlock(&mysql_lock);
-
-	return ret;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "MySQL CDR Backend",
-	.support_level = AST_MODULE_SUPPORT_DEPRECATED,
-	.load = load_module,
-	.unload = unload_module,
-	.reload = reload,
-	.requires = "cdr",
-);
diff --git a/addons/ooh323c/src/ooq931.c b/addons/ooh323c/src/ooq931.c
index fe8b06ebee5081554fea1d6e54bdfbf5a44948bc..b7bcba8469db94367ca270baa069193d3a5d932f 100644
--- a/addons/ooh323c/src/ooq931.c
+++ b/addons/ooh323c/src/ooq931.c
@@ -192,11 +192,13 @@ EXTERN int ooQ931Decode
          screening indicators ;-) */
       if(ie->discriminator == Q931CallingPartyNumberIE)
       {
+         int numoffset=1;
          OOTRACEDBGB1("   CallingPartyNumber IE = {\n");
-         if(ie->length < OO_MAX_NUMBER_LENGTH)
+         if(!(0x80 & ie->data[0])) numoffset = 2;
+
+         if( (ie->length >= numoffset) &&
+             (ie->length < OO_MAX_NUMBER_LENGTH) )
          {
-            int numoffset=1;
-            if(!(0x80 & ie->data[0])) numoffset = 2;
             memcpy(number, ie->data+numoffset,ie->length-numoffset);
             number[ie->length-numoffset]='\0';
             OOTRACEDBGB2("      %s\n", number);
@@ -204,7 +206,7 @@ EXTERN int ooQ931Decode
                ooCallSetCallingPartyNumber(call, number);
          }
          else{
-            OOTRACEERR3("Error:Calling party number too long. (%s, %s)\n",
+            OOTRACEERR3("Error:Calling party number outside range. (%s, %s)\n",
                            call->callType, call->callToken);
          }
          OOTRACEDBGB1("   }\n");
@@ -214,7 +216,8 @@ EXTERN int ooQ931Decode
       if(ie->discriminator == Q931CalledPartyNumberIE)
       {
          OOTRACEDBGB1("   CalledPartyNumber IE = {\n");
-         if(ie->length < OO_MAX_NUMBER_LENGTH)
+         if( (ie->length >= 1) &&
+             (ie->length < OO_MAX_NUMBER_LENGTH) )
          {
             memcpy(number, ie->data+1,ie->length-1);
             number[ie->length-1]='\0';
@@ -223,7 +226,7 @@ EXTERN int ooQ931Decode
                ooCallSetCalledPartyNumber(call, number);
          }
          else{
-            OOTRACEERR3("Error:Calling party number too long. (%s, %s)\n",
+            OOTRACEERR3("Error:Calling party number outside range. (%s, %s)\n",
                            call->callType, call->callToken);
          }
          OOTRACEDBGB1("   }\n");
diff --git a/apps/app_amd.c b/apps/app_amd.c
index 39d0b79a941ebf62930858ca6d7b4d1d428e1de6..fb63c7d54b2723c4482e5e8ea50f03b9123ab4e4 100644
--- a/apps/app_amd.c
+++ b/apps/app_amd.c
@@ -92,6 +92,12 @@
 				<para>Is the maximum duration of a word to accept.</para>
 				<para>If exceeded, then the result is detection as a MACHINE</para>
 			</parameter>
+			<parameter name="audioFile" required="false">
+				<para>Is an audio file to play to the caller while AMD is in progress.</para>
+				<para>By default, no audio file is played.</para>
+				<para>If an audio file is configured in amd.conf, then that file will be used
+				if one is not specified here. That file may be overridden by this argument.</para>
+			</parameter>
 		</syntax>
 		<description>
 			<para>This application attempts to detect answering machines at the beginning
@@ -155,6 +161,9 @@ static int dfltBetweenWordsSilence  = 50;
 static int dfltMaximumNumberOfWords = 2;
 static int dfltSilenceThreshold     = 256;
 static int dfltMaximumWordLength    = 5000; /* Setting this to a large default so it is not used unless specify it in the configs or command line */
+static char *dfltAudioFile = NULL;
+
+static ast_mutex_t config_lock;
 
 /* Set to the lowest ms value provided in amd.conf or application parameters */
 static int dfltMaxWaitTimeForFrame  = 50;
@@ -179,7 +188,7 @@ static void isAnsweringMachine(struct ast_channel *chan, const char *data)
 	char amdCause[256] = "", amdStatus[256] = "";
 	char *parse = ast_strdupa(data);
 
-	/* Lets set the initial values of the variables that will control the algorithm.
+	/* Let's set the initial values of the variables that will control the algorithm.
 	   The initial values are the default ones. If they are passed as arguments
 	   when invoking the application, then the default values will be overwritten
 	   by the ones passed as parameters. */
@@ -193,6 +202,7 @@ static void isAnsweringMachine(struct ast_channel *chan, const char *data)
 	int silenceThreshold     = dfltSilenceThreshold;
 	int maximumWordLength    = dfltMaximumWordLength;
 	int maxWaitTimeForFrame  = dfltMaxWaitTimeForFrame;
+	const char *audioFile = NULL;
 
 	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(argInitialSilence);
@@ -204,8 +214,15 @@ static void isAnsweringMachine(struct ast_channel *chan, const char *data)
 		AST_APP_ARG(argMaximumNumberOfWords);
 		AST_APP_ARG(argSilenceThreshold);
 		AST_APP_ARG(argMaximumWordLength);
+		AST_APP_ARG(audioFile);
 	);
 
+	ast_mutex_lock(&config_lock);
+	if (!ast_strlen_zero(dfltAudioFile)) {
+		audioFile = ast_strdupa(dfltAudioFile);
+	}
+	ast_mutex_unlock(&config_lock);
+
 	ast_verb(3, "AMD: %s %s %s (Fmt: %s)\n", ast_channel_name(chan),
 		S_COR(ast_channel_caller(chan)->ani.number.valid, ast_channel_caller(chan)->ani.number.str, "(N/A)"),
 		S_COR(ast_channel_redirecting(chan)->from.number.valid, ast_channel_redirecting(chan)->from.number.str, "(N/A)"),
@@ -233,6 +250,9 @@ static void isAnsweringMachine(struct ast_channel *chan, const char *data)
 			silenceThreshold = atoi(args.argSilenceThreshold);
 		if (!ast_strlen_zero(args.argMaximumWordLength))
 			maximumWordLength = atoi(args.argMaximumWordLength);
+		if (!ast_strlen_zero(args.audioFile)) {
+			audioFile = args.audioFile;
+		}
 	} else {
 		ast_debug(1, "AMD using the default parameters.\n");
 	}
@@ -280,6 +300,11 @@ static void isAnsweringMachine(struct ast_channel *chan, const char *data)
 	/* Set our start time so we can tie the loop to real world time and not RTP updates */
 	amd_tvstart = ast_tvnow();
 
+	/* Optional audio file to play to caller while AMD is doing its thing. */
+	if (!ast_strlen_zero(audioFile)) {
+		ast_streamfile(chan, audioFile, ast_channel_language(chan));
+	}
+
 	/* Now we go into a loop waiting for frames from the channel */
 	while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) {
 		int ms = 0;
@@ -462,10 +487,14 @@ static void isAnsweringMachine(struct ast_channel *chan, const char *data)
 	/* Free the DSP used to detect silence */
 	ast_dsp_free(silenceDetector);
 
+	/* If we were playing something to pass the time, stop it now. */
+	if (!ast_strlen_zero(audioFile)) {
+		ast_stopstream(chan);
+	}
+
 	return;
 }
 
-
 static int amd_exec(struct ast_channel *chan, const char *data)
 {
 	isAnsweringMachine(chan, data);
@@ -516,7 +545,16 @@ static int load_config(int reload)
 					dfltMaximumNumberOfWords = atoi(var->value);
 				} else if (!strcasecmp(var->name, "maximum_word_length")) {
 					dfltMaximumWordLength = atoi(var->value);
-
+				} else if (!strcasecmp(var->name, "playback_file")) {
+					ast_mutex_lock(&config_lock);
+					if (dfltAudioFile) {
+						ast_free(dfltAudioFile);
+						dfltAudioFile = NULL;
+					}
+					if (!ast_strlen_zero(var->value)) {
+						dfltAudioFile = ast_strdup(var->value);
+					}
+					ast_mutex_unlock(&config_lock);
 				} else {
 					ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
 						app, cat, var->name, var->lineno);
@@ -539,6 +577,12 @@ static int load_config(int reload)
 
 static int unload_module(void)
 {
+	ast_mutex_lock(&config_lock);
+	if (dfltAudioFile) {
+		ast_free(dfltAudioFile);
+	}
+	ast_mutex_unlock(&config_lock);
+	ast_mutex_destroy(&config_lock);
 	return ast_unregister_application(app);
 }
 
@@ -554,6 +598,7 @@ static int unload_module(void)
  */
 static int load_module(void)
 {
+	ast_mutex_init(&config_lock);
 	if (load_config(0) || ast_register_application_xml(app, amd_exec)) {
 		return AST_MODULE_LOAD_DECLINE;
 	}
diff --git a/apps/app_bridgewait.c b/apps/app_bridgewait.c
index 9326ed14fb50c28b8e50fd6a70d0d2d991ac2851..86a4f7b10c4ae80c6127f3943021da4d75b072a0 100644
--- a/apps/app_bridgewait.c
+++ b/apps/app_bridgewait.c
@@ -100,6 +100,9 @@
 						<para>Automatically exit the bridge and return to the PBX after
 						<emphasis>duration</emphasis> seconds.</para>
 					</option>
+					<option name="n">
+						<para>Do not automatically answer the channel.</para>
+					</option>
 				</optionlist>
 			</parameter>
 		</syntax>
@@ -108,7 +111,7 @@
 			The channel will then wait in the holding bridge until some event occurs
 			which removes it from the holding bridge.</para>
 			<note><para>This application will answer calls which haven't already
-			been answered.</para></note>
+			been answered, unless the n option is provided.</para></note>
 		</description>
 	</application>
  ***/
@@ -186,6 +189,7 @@ enum bridgewait_flags {
 	MUXFLAG_MOHCLASS = (1 << 0),
 	MUXFLAG_ENTERTAINMENT = (1 << 1),
 	MUXFLAG_TIMEOUT = (1 << 2),
+	MUXFLAG_NOANSWER = (1 << 3),
 };
 
 enum bridgewait_args {
@@ -199,6 +203,7 @@ AST_APP_OPTIONS(bridgewait_opts, {
 	AST_APP_OPTION_ARG('e', MUXFLAG_ENTERTAINMENT, OPT_ARG_ENTERTAINMENT),
 	AST_APP_OPTION_ARG('m', MUXFLAG_MOHCLASS, OPT_ARG_MOHCLASS),
 	AST_APP_OPTION_ARG('S', MUXFLAG_TIMEOUT, OPT_ARG_TIMEOUT),
+	AST_APP_OPTION('n', MUXFLAG_NOANSWER),
 });
 
 static int bridgewait_timeout_callback(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
@@ -458,7 +463,7 @@ static int bridgewait_exec(struct ast_channel *chan, const char *data)
 	}
 
 	/* Answer the channel if needed */
-	if (ast_channel_state(chan) != AST_STATE_UP) {
+	if (ast_channel_state(chan) != AST_STATE_UP && !ast_test_flag(&flags, MUXFLAG_NOANSWER)) {
 		ast_answer(chan);
 	}
 
diff --git a/apps/app_broadcast.c b/apps/app_broadcast.c
new file mode 100644
index 0000000000000000000000000000000000000000..de4b81db310791fd42e6b30b73e9ccb8185458bf
--- /dev/null
+++ b/apps/app_broadcast.c
@@ -0,0 +1,619 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Naveen Albert
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Channel audio broadcasting
+ *
+ * \author Naveen Albert <asterisk@phreaknet.org>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+	<support_level>extended</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <ctype.h>
+#include <errno.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/audiohook.h"
+#include "asterisk/app.h"
+#include "asterisk/utils.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/options.h"
+#include "asterisk/autochan.h"
+#include "asterisk/format_cache.h"
+#include "asterisk/cli.h" /* use ESS macro */
+
+/*** DOCUMENTATION
+	<application name="Broadcast" language="en_US">
+		<synopsis>
+			Transmit or receive audio to or from multiple channels simultaneously
+		</synopsis>
+		<syntax>
+			<parameter name="options">
+				<optionlist>
+					<option name="b">
+						<para>In addition to broadcasting to target channels, also
+						broadcast to any channels to which target channels are bridged.</para>
+					</option>
+					<option name="l">
+						<para>Allow usage of a long queue to store audio frames.</para>
+						<note><para>This may introduce some delay in the received audio feed, but will improve the audio quality.</para></note>
+					</option>
+					<option name="o">
+						<para>Do not mix streams when combining audio from target channels (only applies with s option).</para>
+					</option>
+					<option name="r">
+						<para>Feed frames to barge channels in "reverse" by injecting them into the primary channel's read queue instead.</para>
+						<para>This option is required for barge to work in a n-party bridge (but not for 2-party bridges). Alternately, you
+						can add an intermediate channel by using a non-optimized Local channel, so that the target channel is bridged with
+						a single channel that is connected to the bridge, but it is recommended this option be used instead.</para>
+						<para>Note that this option will always feed injected audio to the other party, regardless of whether the target
+						channel is bridged or not.</para>
+					</option>
+					<option name="s">
+						<para>Rather than broadcast audio to a bunch of channels, receive the combined audio from the target channels.</para>
+					</option>
+					<option name="w">
+						<para>Broadcast audio received on this channel to other channels.</para>
+					</option>
+				</optionlist>
+			</parameter>
+			<parameter name="channels" required="true" argsep=",">
+				<para>List of channels for broadcast targets.</para>
+				<para>Channel names must be the full channel names, not merely device names.</para>
+				<para>Broadcasting will continue until the broadcasting channel hangs up or all target channels have hung up.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application can be used to broadcast audio to multiple channels at once.
+			Any audio received on this channel will be transmitted to all of the specified channels and, optionally, their bridged peers.</para>
+			<para>It can also be used to aggregate audio from multiple channels at once.
+			Any audio on any of the specified channels, and optionally their bridged peers, will be transmitted to this channel.</para>
+			<para>Execution of the application continues until either the broadcasting channel hangs up
+			or all specified channels have hung up.</para>
+			<para>This application is used for one-to-many and many-to-one audio applications where
+			bridge mixing cannot be done synchronously on all the involved channels.
+			This is primarily useful for injecting the same audio stream into multiple channels at once,
+			or doing the reverse, combining the audio from multiple channels into a single stream.
+			This contrasts with using a separate injection channel for each target channel and/or
+			using a conference bridge.</para>
+			<para>The channel running the Broadcast application must do so synchronously. The specified channels,
+			however, may be doing other things.</para>
+			<example title="Broadcast received audio to three channels and their bridged peers">
+			same => n,Broadcast(wb,DAHDI/1,DAHDI/3,PJSIP/doorphone)
+			</example>
+			<example title="Broadcast received audio to three channels, only">
+			same => n,Broadcast(w,DAHDI/1,DAHDI/3,PJSIP/doorphone)
+			</example>
+			<example title="Combine audio from three channels and their bridged peers to us">
+			same => n,Broadcast(s,DAHDI/1,DAHDI/3,PJSIP/doorphone)
+			</example>
+			<example title="Combine audio from three channels to us">
+			same => n,Broadcast(so,DAHDI/1,DAHDI/3,PJSIP/doorphone)
+			</example>
+			<example title="Two-way audio with a bunch of channels">
+			same => n,Broadcast(wbso,DAHDI/1,DAHDI/3,PJSIP/doorphone)
+			</example>
+			<para>Note that in the last example above, this is NOT the same as a conference bridge.
+			The specified channels are not audible to each other, only to the channel running the
+			Broadcast application. The two-way audio is only between the broadcasting channel and
+			each of the specified channels, individually.</para>
+		</description>
+		<see-also>
+			<ref type="application">ChanSpy</ref>
+		</see-also>
+	</application>
+ ***/
+
+static const char app_broadcast[] = "Broadcast";
+
+enum {
+	OPTION_READONLY          = (1 << 0),    /* Don't mix the two channels */
+	OPTION_BARGE             = (1 << 1),    /* Barge mode (whisper to both channels) */
+	OPTION_LONG_QUEUE        = (1 << 2),	/* Allow usage of a long queue to store audio frames. */
+	OPTION_WHISPER           = (1 << 3),
+	OPTION_SPY               = (1 << 4),
+	OPTION_REVERSE_FEED      = (1 << 5),
+	OPTION_ANSWER_WARN       = (1 << 6),	/* Internal flag, not set by user */
+};
+
+AST_APP_OPTIONS(spy_opts, {
+	AST_APP_OPTION('b', OPTION_BARGE),
+	AST_APP_OPTION('l', OPTION_LONG_QUEUE),
+	AST_APP_OPTION('o', OPTION_READONLY),
+	AST_APP_OPTION('r', OPTION_REVERSE_FEED),
+	AST_APP_OPTION('s', OPTION_SPY),
+	AST_APP_OPTION('w', OPTION_WHISPER),
+});
+
+struct multi_autochan {
+	char *name;
+	struct ast_autochan *autochan;
+	struct ast_autochan *bridge_autochan;
+	struct ast_audiohook whisper_audiohook;
+	struct ast_audiohook bridge_whisper_audiohook;
+	struct ast_audiohook spy_audiohook;
+	unsigned int connected:1;
+	unsigned int bridge_connected:1;
+	unsigned int spying:1;
+	AST_LIST_ENTRY(multi_autochan) entry;	/*!< Next record */
+};
+
+AST_RWLIST_HEAD(multi_autochan_list, multi_autochan);
+
+struct multi_spy {
+	struct multi_autochan_list *chanlist;
+	unsigned int readonly:1;
+};
+
+static void *spy_alloc(struct ast_channel *chan, void *data)
+{
+	return data; /* just store the data pointer in the channel structure */
+}
+
+static void spy_release(struct ast_channel *chan, void *data)
+{
+	return; /* nothing to do */
+}
+
+static int spy_generate(struct ast_channel *chan, void *data, int len, int samples)
+{
+	struct multi_spy *multispy = data;
+	struct multi_autochan_list *chanlist = multispy->chanlist;
+	struct multi_autochan *mac;
+	struct ast_frame *f;
+	short *data1, *data2;
+	int res, i;
+
+	/* All the frames we get are slin, so they will all have the same number of samples. */
+	static const int num_samples = 160;
+	short combine_buf[num_samples];
+	struct ast_frame wf = {
+		.frametype = AST_FRAME_VOICE,
+		.offset = 0,
+		.subclass.format = ast_format_slin,
+		.datalen = num_samples * 2,
+		.samples = num_samples,
+		.src = __FUNCTION__,
+	};
+
+	memset(&combine_buf, 0, sizeof(combine_buf));
+	wf.data.ptr = combine_buf;
+
+	AST_RWLIST_WRLOCK(chanlist);
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(chanlist, mac, entry) {
+		ast_audiohook_lock(&mac->spy_audiohook);
+		if (mac->spy_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
+			ast_audiohook_unlock(&mac->spy_audiohook); /* Channel is already gone more than likely, the broadcasting channel will clean this up. */
+			continue;
+		}
+
+		if (multispy->readonly) { /* Option 'o' was set, so don't mix channel audio */
+			f = ast_audiohook_read_frame(&mac->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_READ, ast_format_slin);
+		} else {
+			f = ast_audiohook_read_frame(&mac->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_BOTH, ast_format_slin);
+		}
+		ast_audiohook_unlock(&mac->spy_audiohook);
+
+		if (!f) {
+			continue; /* No frame? No problem. */
+		}
+
+		/* Mix the samples. */
+		for (i = 0, data1 = combine_buf, data2 = f->data.ptr; i < num_samples; i++, data1++, data2++) {
+			ast_slinear_saturated_add(data1, data2);
+		}
+		ast_frfree(f);
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+	AST_RWLIST_UNLOCK(chanlist);
+
+	res = ast_write(chan, &wf);
+	ast_frfree(&wf);
+
+	return res;
+}
+
+static struct ast_generator spygen = {
+	.alloc = spy_alloc,
+	.release = spy_release,
+	.generate = spy_generate,
+};
+
+static int start_spying(struct ast_autochan *autochan, const char *spychan_name, struct ast_audiohook *audiohook, struct ast_flags *flags)
+{
+	int res;
+
+	ast_autochan_channel_lock(autochan);
+	ast_debug(1, "Attaching spy channel %s to %s\n", spychan_name, ast_channel_name(autochan->chan));
+
+	if (ast_test_flag(flags, OPTION_READONLY)) {
+		ast_set_flag(audiohook, AST_AUDIOHOOK_MUTE_WRITE);
+	} else {
+		ast_set_flag(audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
+	}
+	if (ast_test_flag(flags, OPTION_LONG_QUEUE)) {
+		ast_debug(2, "Using a long queue to store audio frames in spy audiohook\n");
+	} else {
+		ast_set_flag(audiohook, AST_AUDIOHOOK_SMALL_QUEUE);
+	}
+	res = ast_audiohook_attach(autochan->chan, audiohook);
+	ast_autochan_channel_unlock(autochan);
+	return res;
+}
+
+static int attach_barge(struct ast_autochan *spyee_autochan, struct ast_autochan **spyee_bridge_autochan,
+	struct ast_audiohook *bridge_whisper_audiohook, const char *spyer_name, const char *name, struct ast_flags *flags)
+{
+	int retval = 0;
+	struct ast_autochan *internal_bridge_autochan;
+	struct ast_channel *spyee_chan;
+	RAII_VAR(struct ast_channel *, bridged, NULL, ast_channel_cleanup);
+
+	ast_autochan_channel_lock(spyee_autochan);
+	spyee_chan = ast_channel_ref(spyee_autochan->chan);
+	ast_autochan_channel_unlock(spyee_autochan);
+
+	/* Note that ast_channel_bridge_peer only returns non-NULL for 2-party bridges, not n-party bridges (e.g. ConfBridge) */
+	bridged = ast_channel_bridge_peer(spyee_chan);
+	ast_channel_unref(spyee_chan);
+	if (!bridged) {
+		ast_debug(9, "Channel %s is not yet bridged, unable to setup barge\n", ast_channel_name(spyee_chan));
+		/* If we're bridged, but it's not a 2-party bridge, then we probably should have used OPTION_REVERSE_FEED. */
+		if (ast_test_flag(flags, OPTION_ANSWER_WARN) && ast_channel_is_bridged(spyee_chan)) {
+			ast_clear_flag(flags, OPTION_ANSWER_WARN); /* Don't warn more than once. */
+			ast_log(LOG_WARNING, "Barge failed: channel is bridged, but not to a 2-party bridge. Use the 'r' option.\n");
+		}
+		return -1;
+	}
+
+	ast_audiohook_init(bridge_whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "Broadcast", 0);
+	internal_bridge_autochan = ast_autochan_setup(bridged);
+	if (!internal_bridge_autochan) {
+		return -1;
+	}
+
+	if (start_spying(internal_bridge_autochan, spyer_name, bridge_whisper_audiohook, flags)) {
+		ast_log(LOG_WARNING, "Unable to attach barge audiohook on spyee '%s'. Barge mode disabled.\n", name);
+		retval = -1;
+	}
+
+	*spyee_bridge_autochan = internal_bridge_autochan;
+	return retval;
+}
+
+static void multi_autochan_free(struct multi_autochan *mac)
+{
+	if (mac->connected) {
+		if (mac->whisper_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
+			ast_debug(2, "Whisper audiohook no longer running\n");
+		}
+		ast_audiohook_lock(&mac->whisper_audiohook);
+		ast_audiohook_detach(&mac->whisper_audiohook);
+		ast_audiohook_unlock(&mac->whisper_audiohook);
+		ast_audiohook_destroy(&mac->whisper_audiohook);
+	}
+	if (mac->bridge_connected) {
+		if (mac->bridge_whisper_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
+			ast_debug(2, "Whisper (bridged) audiohook no longer running\n");
+		}
+		ast_audiohook_lock(&mac->bridge_whisper_audiohook);
+		ast_audiohook_detach(&mac->bridge_whisper_audiohook);
+		ast_audiohook_unlock(&mac->bridge_whisper_audiohook);
+		ast_audiohook_destroy(&mac->bridge_whisper_audiohook);
+	}
+	if (mac->spying) {
+		if (mac->spy_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
+			ast_debug(2, "Spy audiohook no longer running\n");
+		}
+		ast_audiohook_lock(&mac->spy_audiohook);
+		ast_audiohook_detach(&mac->spy_audiohook);
+		ast_audiohook_unlock(&mac->spy_audiohook);
+		ast_audiohook_destroy(&mac->spy_audiohook);
+	}
+	if (mac->name) {
+		int total = mac->connected + mac->bridge_connected + mac->spying;
+		ast_debug(1, "Removing channel %s from target list (%d hook%s)\n", mac->name, total, ESS(total));
+		ast_free(mac->name);
+	}
+	if (mac->autochan) {
+		ast_autochan_destroy(mac->autochan);
+	}
+	if (mac->bridge_autochan) {
+		ast_autochan_destroy(mac->bridge_autochan);
+	}
+	ast_free(mac);
+}
+
+static int do_broadcast(struct ast_channel *chan, struct ast_flags *flags, const char *channels)
+{
+	int res = 0;
+	struct ast_frame *f;
+	struct ast_silence_generator *silgen = NULL;
+	struct multi_spy multispy;
+	struct multi_autochan_list chanlist;
+	struct multi_autochan *mac;
+	int numchans = 0;
+	int readonly = ast_test_flag(flags, OPTION_READONLY) ? 1 : 0;
+	char *next, *chansdup = ast_strdupa(channels);
+
+	AST_RWLIST_HEAD_INIT(&chanlist);
+	ast_channel_set_flag(chan, AST_FLAG_SPYING);
+
+	ast_set_flag(flags, OPTION_ANSWER_WARN); /* Initialize answer warn to 1 */
+
+	/* Hey, look ma, no list lock needed! Sometimes, it's nice to not have to share... */
+
+	/* Build a list of targets */
+	while ((next = strsep(&chansdup, ","))) {
+		struct ast_channel *ochan;
+		if (ast_strlen_zero(next)) {
+			continue;
+		}
+		if (!strcmp(next, ast_channel_name(chan))) {
+			ast_log(LOG_WARNING, "Refusing to broadcast to ourself: %s\n", next);
+			continue;
+		}
+		ochan = ast_channel_get_by_name(next);
+		if (!ochan) {
+			ast_log(LOG_WARNING, "No such channel: %s\n", next);
+			continue;
+		}
+		/* Append to end of list. */
+		if (!(mac = ast_calloc(1, sizeof(*mac)))) {
+			ast_log(LOG_WARNING, "Multi autochan allocation failure\n");
+			continue;
+		}
+		mac->name = ast_strdup(next);
+		mac->autochan = ast_autochan_setup(ochan);
+		if (!mac->name || !mac->autochan) {
+			multi_autochan_free(mac);
+			continue;
+		}
+		if (ast_test_flag(flags, OPTION_WHISPER)) {
+			mac->connected = 1;
+			ast_audiohook_init(&mac->whisper_audiohook, AST_AUDIOHOOK_TYPE_WHISPER, "Broadcast", 0);
+			/* Inject audio from our channel to this target. */
+			if (start_spying(mac->autochan, next, &mac->whisper_audiohook, flags)) {
+				ast_log(LOG_WARNING, "Unable to attach whisper audiohook to %s\n", next);
+				multi_autochan_free(mac);
+				continue;
+			}
+		}
+		if (ast_test_flag(flags, OPTION_SPY)) {
+			mac->spying = 1;
+			ast_audiohook_init(&mac->spy_audiohook, AST_AUDIOHOOK_TYPE_SPY, "Broadcast", 0);
+			if (start_spying(mac->autochan, next, &mac->spy_audiohook, flags)) {
+				ast_log(LOG_WARNING, "Unable to attach spy audiohook to %s\n", next);
+				multi_autochan_free(mac);
+				continue;
+			}
+		}
+		AST_RWLIST_INSERT_TAIL(&chanlist, mac, entry);
+		numchans++;
+		ochan = ast_channel_unref(ochan);
+	}
+
+	ast_verb(4, "Broadcasting to %d channel%s on %s\n", numchans, ESS(numchans), ast_channel_name(chan));
+	ast_debug(1, "Broadcasting: (TX->1) whisper=%d, (TX->2) barge=%d, (RX<-%d) spy=%d (%s)\n",
+		ast_test_flag(flags, OPTION_WHISPER) ? 1 : 0,
+		ast_test_flag(flags, OPTION_BARGE) ? 1 : 0,
+		readonly ? 1 : 2,
+		ast_test_flag(flags, OPTION_SPY) ? 1 : 0,
+		readonly ? "single" : "both");
+
+	if (ast_test_flag(flags, OPTION_SPY)) {
+		multispy.chanlist = &chanlist;
+		multispy.readonly = readonly;
+		ast_activate_generator(chan, &spygen, &multispy);
+	} else {
+		/* We're not expecting to read any audio, just broadcast audio to a bunch of other channels. */
+		silgen = ast_channel_start_silence_generator(chan);
+	}
+
+	while (numchans && ast_waitfor(chan, -1) > 0) {
+		int fres = 0;
+		f = ast_read(chan);
+		if (!f) {
+			ast_debug(1, "Channel %s must have hung up\n", ast_channel_name(chan));
+			res = -1;
+			break;
+		}
+		if (f->frametype != AST_FRAME_VOICE) { /* Ignore any non-voice frames */
+			ast_frfree(f);
+			continue;
+		}
+		/* Write the frame to all our targets. */
+		AST_RWLIST_WRLOCK(&chanlist);
+		AST_RWLIST_TRAVERSE_SAFE_BEGIN(&chanlist, mac, entry) {
+			/* Note that if no media is received, execution is suspended, but assuming continuous or
+			 * or frequent audio on the broadcasting channel, we'll quickly enough detect hung up targets.
+			 * This isn't really an issue, just something that might be confusing at first, but this is
+			 * due to the limitation with audiohooks of using the channel for timing. */
+			if ((ast_test_flag(flags, OPTION_WHISPER) && mac->whisper_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
+				|| (ast_test_flag(flags, OPTION_SPY) && mac->spy_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)
+				|| (mac->bridge_connected && ast_test_flag(flags, OPTION_BARGE) && mac->bridge_whisper_audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING)) {
+				/* Even if we're spying only and not actually broadcasting audio, we need to detect channel hangup. */
+				AST_RWLIST_REMOVE_CURRENT(entry);
+				ast_debug(2, "Looks like %s has hung up\n", mac->name);
+				multi_autochan_free(mac);
+				numchans--;
+				ast_debug(2, "%d channel%s remaining in broadcast on %s\n", numchans, ESS(numchans), ast_channel_name(chan));
+				continue;
+			}
+
+			if (ast_test_flag(flags, OPTION_WHISPER)) {
+				ast_audiohook_lock(&mac->whisper_audiohook);
+				fres |= ast_audiohook_write_frame(&mac->whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
+				ast_audiohook_unlock(&mac->whisper_audiohook);
+			}
+
+			if (ast_test_flag(flags, OPTION_BARGE)) {
+				/* This hook lets us inject audio into the channel that the spyee is currently
+				 * bridged with. If the spyee isn't bridged with anything yet, nothing will
+				 * be attached and we'll need to continue attempting to attach the barge
+				 * audio hook.
+				 * The exception to this is if we are emulating barge by doing it "directly",
+				 * that is injecting the frames onto this channel's read queue, rather than
+				 * its bridged peer's write queue, then skip this. We only do one or the other. */
+				if (!ast_test_flag(flags, OPTION_REVERSE_FEED) && !mac->bridge_connected && !attach_barge(mac->autochan, &mac->bridge_autochan,
+						&mac->bridge_whisper_audiohook, ast_channel_name(chan), mac->name, flags)) {
+					ast_debug(2, "Attached barge channel for %s\n", mac->name);
+					mac->bridge_connected = 1;
+				}
+
+				if (mac->bridge_connected) {
+					ast_audiohook_lock(&mac->bridge_whisper_audiohook);
+					fres |= ast_audiohook_write_frame(&mac->bridge_whisper_audiohook, AST_AUDIOHOOK_DIRECTION_WRITE, f);
+					ast_audiohook_unlock(&mac->bridge_whisper_audiohook);
+				} else if (ast_test_flag(flags, OPTION_REVERSE_FEED)) {
+					/* So, this is really clever...
+					 * If we're connected to an n-party bridge instead of a 2-party bridge,
+					 * attach_barge will ALWAYS fail because we're connected to a bridge, not
+					 * a single peer channel.
+					 * Recall that the objective is for injected audio to be audible to both
+					 * sides of the channel. So really, the typical way of doing this by
+					 * directly injecting frames separately onto both channels is kind of
+					 * bizarre to begin with, when you think about it.
+					 *
+					 * In other words, this is how ChanSpy and this module by default work:
+					 * We have audio F to inject onto channels A and B, which are <= bridged =>:
+					 * READ <- A -> WRITE <==> READ <- B -> WRITE
+					 *            F --^                  F --^
+					 *
+					 * So that makes the same audio audible to both channels A and B, but
+					 * in kind of a roundabout way. What if the bridged peer changes at
+					 * some point, for example?
+					 *
+					 * While that method works for 2-party bridges, it doesn't work at all
+					 * for an n-party bridge, so we do the thing that seems obvious to begin with:
+					 * dump the frames onto THIS channel's read queue, and the channels will
+					 * make their way into the bridge like any other audio from this channel,
+					 * and everything just works perfectly, no matter what kind of bridging
+					 * scenario is being used. At that point, we don't even care if we're
+					 * bridged or not, and really, why should we?
+					 *
+					 * In other words, we do this:
+					 * READ <- A -> WRITE <==> READ <- B -> WRITE
+					 *                       F --^       F --^
+					 */
+					ast_audiohook_lock(&mac->whisper_audiohook);
+					fres |= ast_audiohook_write_frame(&mac->whisper_audiohook, AST_AUDIOHOOK_DIRECTION_READ, f);
+					ast_audiohook_unlock(&mac->whisper_audiohook);
+				}
+			}
+			if (fres) {
+				ast_log(LOG_WARNING, "Failed to write to audiohook for %s\n", mac->name);
+				fres = 0;
+			}
+		}
+		AST_RWLIST_TRAVERSE_SAFE_END;
+		AST_RWLIST_UNLOCK(&chanlist);
+		ast_frfree(f);
+	}
+
+	if (!numchans) {
+		ast_debug(1, "Exiting due to all target channels having left the broadcast\n");
+	}
+
+	if (ast_test_flag(flags, OPTION_SPY)) {
+		ast_deactivate_generator(chan);
+	} else {
+		ast_channel_stop_silence_generator(chan, silgen);
+	}
+
+	/* Cleanup any remaining targets */
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&chanlist, mac, entry) {
+		AST_RWLIST_REMOVE_CURRENT(entry);
+		multi_autochan_free(mac);
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+
+	ast_channel_clear_flag(chan, AST_FLAG_SPYING);
+	return res;
+}
+
+static int broadcast_exec(struct ast_channel *chan, const char *data)
+{
+	struct ast_flags flags;
+	struct ast_format *write_format;
+	int res = -1;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(options);
+		AST_APP_ARG(channels); /* Channel list last, so we can have multiple */
+	);
+	char *parse = NULL;
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "Broadcast requires at least one channel\n");
+		return -1;
+	}
+
+	parse = ast_strdupa(data);
+	AST_STANDARD_APP_ARGS(args, parse);
+
+	if (ast_strlen_zero(args.channels)) {
+		ast_log(LOG_WARNING, "Must specify at least one channel for broadcast\n");
+		return -1;
+	}
+	if (args.options) {
+		ast_app_parse_options(spy_opts, &flags, NULL, args.options);
+	} else {
+		ast_clear_flag(&flags, AST_FLAGS_ALL);
+	}
+
+	if (!ast_test_flag(&flags, OPTION_BARGE) && !ast_test_flag(&flags, OPTION_SPY) && !ast_test_flag(&flags, OPTION_WHISPER)) {
+		ast_log(LOG_WARNING, "At least one of the b, s, or w option must be specified (provided options have no effect)\n");
+		return -1;
+	}
+
+	write_format = ao2_bump(ast_channel_writeformat(chan));
+	if (ast_set_write_format(chan, ast_format_slin) < 0) {
+		ast_log(LOG_ERROR, "Failed to set write format to slin.\n");
+		goto cleanup;
+	}
+
+	res = do_broadcast(chan, &flags, args.channels);
+
+	/* Restore previous write format */
+	if (ast_set_write_format(chan, write_format)) {
+		ast_log(LOG_ERROR, "Failed to restore write format for channel %s\n", ast_channel_name(chan));
+	}
+
+cleanup:
+	ao2_ref(write_format, -1);
+	return res;
+}
+
+static int unload_module(void)
+{
+	return ast_unregister_application(app_broadcast);
+}
+
+static int load_module(void)
+{
+	return ast_register_application_xml(app_broadcast, broadcast_exec);
+}
+
+AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Channel Audio Broadcasting");
diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c
index e0a0b4da5d4d04d2bbcd9f828b6dd3678e9af5b4..dd683d35c62cbbe56f2a453660a57617e94e6677 100644
--- a/apps/app_confbridge.c
+++ b/apps/app_confbridge.c
@@ -128,9 +128,15 @@
 			<ref type="application">ConfKick</ref>
 			<ref type="function">CONFBRIDGE</ref>
 			<ref type="function">CONFBRIDGE_INFO</ref>
+			<ref type="function">CONFBRIDGE_CHANNELS</ref>
 		</see-also>
 	</application>
 	<application name="ConfKick" language="en_US">
+		<since>
+			<version>16.19.0</version>
+			<version>18.5.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Kicks channel(s) from the requested ConfBridge.
 		</synopsis>
@@ -159,6 +165,7 @@
 			<ref type="application">ConfBridge</ref>
 			<ref type="function">CONFBRIDGE</ref>
 			<ref type="function">CONFBRIDGE_INFO</ref>
+			<ref type="function">CONFBRIDGE_CHANNELS</ref>
 		</see-also>
 	</application>
 	<function name="CONFBRIDGE" language="en_US">
@@ -191,23 +198,29 @@
 			<para>---- Example 1 ----</para>
 			<para>In this example the custom user profile set on the channel will
 			automatically be used by the ConfBridge application.</para>
-			<para>exten => 1,1,Answer()</para>
+			<example title="Example 1">
+			exten => 1,1,Answer()
+			</example>
 			<para>; In this example the effect of the following line is</para>
 			<para>; implied:</para>
-			<para>; same => n,Set(CONFBRIDGE(user,template)=default_user)</para>
-			<para>same => n,Set(CONFBRIDGE(user,announce_join_leave)=yes)</para>
-			<para>same => n,Set(CONFBRIDGE(user,startmuted)=yes)</para>
-			<para>same => n,ConfBridge(1) </para>
+			<example title="Example 1b">
+			same => n,Set(CONFBRIDGE(user,template)=default_user)
+			same => n,Set(CONFBRIDGE(user,announce_join_leave)=yes)
+			same => n,Set(CONFBRIDGE(user,startmuted)=yes)
+			same => n,ConfBridge(1)
+			</example>
 			<para>---- Example 2 ----</para>
 			<para>This example shows how to use a predefined user profile in
 			<filename>confbridge.conf</filename> as a template for a dynamic profile.
 			Here we make an admin/marked user out of the <literal>my_user</literal>
 			profile that you define in <filename>confbridge.conf</filename>.</para>
-			<para>exten => 1,1,Answer()</para>
-			<para>same => n,Set(CONFBRIDGE(user,template)=my_user)</para>
-			<para>same => n,Set(CONFBRIDGE(user,admin)=yes)</para>
-			<para>same => n,Set(CONFBRIDGE(user,marked)=yes)</para>
-			<para>same => n,ConfBridge(1)</para>
+			<example title="Example 2">
+			exten => 1,1,Answer()
+			same => n,Set(CONFBRIDGE(user,template)=my_user)
+			same => n,Set(CONFBRIDGE(user,admin)=yes)
+			same => n,Set(CONFBRIDGE(user,marked)=yes)
+			same => n,ConfBridge(1)
+			</example>
 		</description>
 	</function>
 	<function name="CONFBRIDGE_INFO" language="en_US">
@@ -243,6 +256,50 @@
 			<para>This function returns a non-negative integer for valid conference
 			names and an empty string for invalid conference names.</para>
 		</description>
+		<see-also>
+			<ref type="function">CONFBRIDGE_CHANNELS</ref>
+		</see-also>
+	</function>
+	<function name="CONFBRIDGE_CHANNELS" language="en_US">
+		<since>
+			<version>16.26.0</version>
+			<version>18.12.0</version>
+			<version>19.4.0</version>
+		</since>
+		<synopsis>
+			Get a list of channels in a ConfBridge conference.
+		</synopsis>
+		<syntax>
+			<parameter name="type" required="true">
+				<para>What conference information is requested.</para>
+				<enumlist>
+					<enum name="admins">
+						<para>Get the number of admin users in the conference.</para>
+					</enum>
+					<enum name="marked">
+						<para>Get the number of marked users in the conference.</para>
+					</enum>
+					<enum name="parties">
+						<para>Get the number of total users in the conference.</para>
+					</enum>
+					<enum name="active">
+						<para>Get the number of active users in the conference.</para>
+					</enum>
+					<enum name="waiting">
+						<para>Get the number of waiting users in the conference.</para>
+					</enum>
+				</enumlist>
+			</parameter>
+			<parameter name="conf" required="true">
+				<para>The name of the conference being referenced.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This function returns a comma-separated list of channels in a ConfBridge conference, optionally filtered by a type of participant.</para>
+		</description>
+		<see-also>
+			<ref type="function">CONFBRIDGE_INFO</ref>
+		</see-also>
 	</function>
 	<manager name="ConfbridgeList" language="en_US">
 		<synopsis>
@@ -336,6 +393,37 @@
 				ConfbridgeListRoomsComplete.</para>
 		</description>
 	</manager>
+	<managerEvent language="en_US" name="ConfbridgeListRooms">
+		<managerEventInstance class="EVENT_FLAG_REPORTING">
+			<synopsis>Raised as part of the ConfbridgeListRooms action response list.</synopsis>
+			<syntax>
+				<parameter name="Conference">
+					<para>The name of the Confbridge conference.</para>
+				</parameter>
+				<parameter name="Parties">
+					<para>Number of users in the conference.</para>
+					<para>This includes both active and waiting users.</para>
+				</parameter>
+				<parameter name="Marked">
+					<para>Number of marked users in the conference.</para>
+				</parameter>
+				<parameter name="Locked">
+					<para>Is the conference locked?</para>
+					<enumlist>
+						<enum name="Yes"/>
+						<enum name="No"/>
+					</enumlist>
+				</parameter>
+				<parameter name="Muted">
+					<para>Is the conference muted?</para>
+					<enumlist>
+						<enum name="Yes"/>
+						<enum name="No"/>
+					</enumlist>
+				</parameter>
+			</syntax>
+		</managerEventInstance>
+	</managerEvent>
 	<manager name="ConfbridgeMute" language="en_US">
 		<synopsis>
 			Mute a Confbridge user.
@@ -1681,7 +1769,7 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen
 	struct post_join_action *action;
 	int max_members_reached = 0;
 
-	/* We explicitly lock the conference bridges container ourselves so that other callers can not create duplicate conferences at the same */
+	/* We explicitly lock the conference bridges container ourselves so that other callers can not create duplicate conferences at the same time */
 	ao2_lock(conference_bridges);
 
 	ast_debug(1, "Trying to find conference bridge '%s'\n", conference_name);
@@ -1747,7 +1835,6 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen
 			ast_bridge_set_talker_src_video_mode(conference->bridge);
 		} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_SFU)) {
 			ast_bridge_set_sfu_video_mode(conference->bridge);
-			ast_bridge_set_video_update_discard(conference->bridge, conference->b_profile.video_update_discard);
 			ast_bridge_set_remb_send_interval(conference->bridge, conference->b_profile.remb_send_interval);
 			if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE)) {
 				ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_AVERAGE);
@@ -1767,6 +1854,9 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen
 			}
 		}
 
+		/* Always set the minimum interval between video updates, to avoid infinite video updates. */
+		ast_bridge_set_video_update_discard(conference->bridge, conference->b_profile.video_update_discard);
+
 		if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_ENABLE_EVENTS)) {
 			ast_bridge_set_send_sdp_label(conference->bridge, 1);
 		}
@@ -2720,17 +2810,24 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
 		ast_autoservice_stop(chan);
 	}
 
-	/* Play the Join sound to both the conference and the user entering. */
 	if (!quiet) {
 		const char *join_sound = conf_get_sound(CONF_SOUND_JOIN, conference->b_profile.sounds);
 
-		if (strcmp(conference->b_profile.language, ast_channel_language(chan))) {
-			ast_stream_and_wait(chan, join_sound, "");
+		/* if hear_own_join_sound is enabled play the Join sound to everyone */
+		if (ast_test_flag(&user.u_profile, USER_OPT_HEAR_OWN_JOIN_SOUND) ) {
+			if (strcmp(conference->b_profile.language, ast_channel_language(chan))) {
+				ast_stream_and_wait(chan, join_sound, "");
+				ast_autoservice_start(chan);
+				play_sound_file(conference, join_sound);
+				ast_autoservice_stop(chan);
+			} else {
+				async_play_sound_file(conference, join_sound, chan);
+			}
+		/* if hear_own_join_sound is disabled only play the Join sound to just the conference */
+		} else {
 			ast_autoservice_start(chan);
 			play_sound_file(conference, join_sound);
 			ast_autoservice_stop(chan);
-		} else {
-			async_play_sound_file(conference, join_sound, chan);
 		}
 	}
 
@@ -3817,6 +3914,90 @@ static struct ast_custom_function confbridge_info_function = {
 	.read = func_confbridge_info,
 };
 
+static int func_confbridge_channels(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+	char *parse, *outbuf;
+	struct confbridge_conference *conference;
+	struct confbridge_user *user;
+	int bytes, count = 0;
+	size_t outlen;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(type);
+		AST_APP_ARG(confno);
+	);
+
+	/* parse all the required arguments and make sure they exist. */
+	if (ast_strlen_zero(data)) {
+		return -1;
+	}
+	parse = ast_strdupa(data);
+	AST_STANDARD_APP_ARGS(args, parse);
+	if (ast_strlen_zero(args.confno) || ast_strlen_zero(args.type)) {
+		ast_log(LOG_WARNING, "Usage: %s(category,confno)", cmd);
+		return -1;
+	}
+	conference = ao2_find(conference_bridges, args.confno, OBJ_KEY);
+	if (!conference) {
+		ast_debug(1, "No such conference: %s\n", args.confno);
+		return -1;
+	}
+
+	outbuf = buf;
+	outlen = len;
+
+	ao2_lock(conference);
+	if (!strcasecmp(args.type, "parties")) {
+		AST_LIST_TRAVERSE(&conference->active_list, user, list) {
+			bytes = snprintf(outbuf, outlen, "%s%s", count++ ? "," : "", ast_channel_name(user->chan));
+			outbuf += bytes;
+			outlen -= bytes;
+		}
+		AST_LIST_TRAVERSE(&conference->waiting_list, user, list) {
+			bytes = snprintf(outbuf, outlen, "%s%s", count++ ? "," : "", ast_channel_name(user->chan));
+			outbuf += bytes;
+			outlen -= bytes;
+		}
+	} else if (!strcasecmp(args.type, "active")) {
+		AST_LIST_TRAVERSE(&conference->active_list, user, list) {
+			bytes = snprintf(outbuf, outlen, "%s%s", count++ ? "," : "", ast_channel_name(user->chan));
+			outbuf += bytes;
+			outlen -= bytes;
+		}
+	} else if (!strcasecmp(args.type, "waiting")) {
+		AST_LIST_TRAVERSE(&conference->waiting_list, user, list) {
+			bytes = snprintf(outbuf, outlen, "%s%s", count++ ? "," : "", ast_channel_name(user->chan));
+			outbuf += bytes;
+			outlen -= bytes;
+		}
+	} else if (!strcasecmp(args.type, "admins")) {
+		AST_LIST_TRAVERSE(&conference->active_list, user, list) {
+			if (ast_test_flag(&user->u_profile, USER_OPT_ADMIN)) {
+				bytes = snprintf(outbuf, outlen, "%s%s", count++ ? "," : "", ast_channel_name(user->chan));
+				outbuf += bytes;
+				outlen -= bytes;
+			}
+		}
+	} else if (!strcasecmp(args.type, "marked")) {
+		AST_LIST_TRAVERSE(&conference->active_list, user, list) {
+			if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
+				bytes = snprintf(outbuf, outlen, "%s%s", count++ ? "," : "", ast_channel_name(user->chan));
+				outbuf += bytes;
+				outlen -= bytes;
+			}
+		}
+	} else {
+		ast_log(LOG_ERROR, "Invalid keyword '%s' passed to %s.\n", args.type, cmd);
+	}
+	ao2_unlock(conference);
+	ao2_ref(conference, -1);
+	return 0;
+}
+
+static struct ast_custom_function confbridge_channels_function = {
+	.name = "CONFBRIDGE_CHANNELS",
+	.read = func_confbridge_channels,
+};
+
 static int action_confbridgelist_item(struct mansession *s, const char *id_text, struct confbridge_conference *conference, struct confbridge_user *user, int waiting)
 {
 	struct ast_channel_snapshot *snapshot;
@@ -3841,6 +4022,7 @@ static int action_confbridgelist_item(struct mansession *s, const char *id_text,
 		"MarkedUser: %s\r\n"
 		"WaitMarked: %s\r\n"
 		"EndMarked: %s\r\n"
+		"EndMarkedAny: %s\r\n"
 		"Waiting: %s\r\n"
 		"Muted: %s\r\n"
 		"Talking: %s\r\n"
@@ -3853,6 +4035,7 @@ static int action_confbridgelist_item(struct mansession *s, const char *id_text,
 		AST_YESNO(ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)),
 		AST_YESNO(ast_test_flag(&user->u_profile, USER_OPT_WAITMARKED)),
 		AST_YESNO(ast_test_flag(&user->u_profile, USER_OPT_ENDMARKED)),
+		AST_YESNO(ast_test_flag(&user->u_profile, USER_OPT_ENDMARKEDANY)),
 		AST_YESNO(waiting),
 		AST_YESNO(user->muted),
 		AST_YESNO(user->talking),
@@ -4394,6 +4577,7 @@ static int unload_module(void)
 
 	ast_custom_function_unregister(&confbridge_function);
 	ast_custom_function_unregister(&confbridge_info_function);
+	ast_custom_function_unregister(&confbridge_channels_function);
 
 	ast_cli_unregister_multiple(cli_confbridge, ARRAY_LEN(cli_confbridge));
 
@@ -4465,6 +4649,7 @@ static int load_module(void)
 
 	res |= ast_custom_function_register_escalating(&confbridge_function, AST_CFE_WRITE);
 	res |= ast_custom_function_register(&confbridge_info_function);
+	res |= ast_custom_function_register(&confbridge_channels_function);
 
 	res |= ast_cli_register_multiple(cli_confbridge, ARRAY_LEN(cli_confbridge));
 
diff --git a/apps/app_dahdiras.c b/apps/app_dahdiras.c
deleted file mode 100644
index d2ec137b4fb217f78bcb39dbbc6308481c731666..0000000000000000000000000000000000000000
--- a/apps/app_dahdiras.c
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2005, Digium, Inc.
- *
- * Mark Spencer <markster@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Execute an ISDN RAS
- *
- * \author Mark Spencer <markster@digium.com>
- *
- * \ingroup applications
- */
-
-/*** MODULEINFO
-	<depend>dahdi</depend>
-	<support_level>deprecated</support_level>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include "asterisk.h"
-
-#include <sys/ioctl.h>
-#include <sys/wait.h>
-#include <signal.h>
-#include <fcntl.h>
-
-#include <dahdi/user.h>
-
-#include "asterisk/lock.h"
-#include "asterisk/file.h"
-#include "asterisk/channel.h"
-#include "asterisk/pbx.h"
-#include "asterisk/module.h"
-#include "asterisk/app.h"
-
-/*** DOCUMENTATION
-	<application name="DAHDIRAS" language="en_US">
-		<synopsis>
-			Executes DAHDI ISDN RAS application.
-		</synopsis>
-		<syntax>
-			<parameter name="args" required="true">
-				<para>A list of parameters to pass to the pppd daemon,
-				separated by <literal>,</literal> characters.</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>Executes a RAS server using pppd on the given channel.
-			The channel must be a clear channel (i.e. PRI source) and a DAHDI
-			channel to be able to use this function (No modem emulation is included).</para>
-			<para>Your pppd must be patched to be DAHDI aware.</para>
-		</description>
-	</application>
-
- ***/
-
-static const char app[] = "DAHDIRAS";
-
-#define PPP_MAX_ARGS	32
-#define PPP_EXEC	"/usr/sbin/pppd"
-
-static pid_t spawn_ras(struct ast_channel *chan, char *args)
-{
-	pid_t pid;
-	char *c;
-
-	char *argv[PPP_MAX_ARGS];
-	int argc = 0;
-	char *stringp=NULL;
-
-	/* Start by forking */
-	pid = ast_safe_fork(1);
-	if (pid) {
-		return pid;
-	}
-
-	/* Execute RAS on File handles */
-	dup2(ast_channel_fd(chan, 0), STDIN_FILENO);
-
-	/* Drop high priority */
-	if (ast_opt_high_priority)
-		ast_set_priority(0);
-
-	/* Close other file descriptors */
-	ast_close_fds_above_n(STDERR_FILENO);
-
-	/* Reset all arguments */
-	memset(argv, 0, sizeof(argv));
-
-	/* First argument is executable, followed by standard
-	   arguments for DAHDI PPP */
-	argv[argc++] = PPP_EXEC;
-	argv[argc++] = "nodetach";
-
-	/* And all the other arguments */
-	stringp=args;
-	c = strsep(&stringp, ",");
-	while(c && strlen(c) && (argc < (PPP_MAX_ARGS - 4))) {
-		argv[argc++] = c;
-		c = strsep(&stringp, ",");
-	}
-
-	if (geteuid() == 0) {
-		argv[argc++] = "plugin";
-		argv[argc++] = "dahdi.so";
-	}
-	argv[argc++] = "stdin";
-
-	/* Finally launch PPP */
-	execv(PPP_EXEC, argv);
-	fprintf(stderr, "Failed to exec PPPD!\n");
-	exit(1);
-}
-
-static void run_ras(struct ast_channel *chan, char *args)
-{
-	pid_t pid;
-	int status;
-	int res;
-	int signalled = 0;
-	struct dahdi_bufferinfo savebi;
-	int x;
-
-	res = ioctl(ast_channel_fd(chan, 0), DAHDI_GET_BUFINFO, &savebi);
-	if(res) {
-		ast_log(LOG_WARNING, "Unable to check buffer policy on channel %s\n", ast_channel_name(chan));
-		return;
-	}
-
-	pid = spawn_ras(chan, args);
-	if (pid < 0) {
-		ast_log(LOG_WARNING, "Failed to spawn RAS\n");
-	} else {
-		for (;;) {
-			res = waitpid(pid, &status, WNOHANG);
-			if (!res) {
-				/* Check for hangup */
-				if (ast_check_hangup(chan) && !signalled) {
-					ast_debug(1, "Channel '%s' hungup.  Signalling RAS at %d to die...\n", ast_channel_name(chan), pid);
-					kill(pid, SIGTERM);
-					signalled=1;
-				}
-				/* Try again */
-				sleep(1);
-				continue;
-			}
-			if (res < 0) {
-				ast_log(LOG_WARNING, "waitpid returned %d: %s\n", res, strerror(errno));
-			}
-			if (WIFEXITED(status)) {
-				ast_verb(3, "RAS on %s terminated with status %d\n", ast_channel_name(chan), WEXITSTATUS(status));
-			} else if (WIFSIGNALED(status)) {
-				ast_verb(3, "RAS on %s terminated with signal %d\n",
-					 ast_channel_name(chan), WTERMSIG(status));
-			} else {
-				ast_verb(3, "RAS on %s terminated weirdly.\n", ast_channel_name(chan));
-			}
-			/* Throw back into audio mode */
-			x = 1;
-			ioctl(ast_channel_fd(chan, 0), DAHDI_AUDIOMODE, &x);
-
-			/* Restore saved values */
-			res = ioctl(ast_channel_fd(chan, 0), DAHDI_SET_BUFINFO, &savebi);
-			if (res < 0) {
-				ast_log(LOG_WARNING, "Unable to set buffer policy on channel %s\n", ast_channel_name(chan));
-			}
-			break;
-		}
-	}
-	ast_safe_fork_cleanup();
-}
-
-static int dahdiras_exec(struct ast_channel *chan, const char *data)
-{
-	int res=-1;
-	char *args;
-	struct dahdi_params dahdip;
-
-	if (!data)
-		data = "";
-
-	args = ast_strdupa(data);
-
-	/* Answer the channel if it's not up */
-	if (ast_channel_state(chan) != AST_STATE_UP)
-		ast_answer(chan);
-	if (strcasecmp(ast_channel_tech(chan)->type, "DAHDI")) {
-		/* If it's not a DAHDI channel, we're done.  Wait a couple of
-		   seconds and then hangup... */
-		ast_verb(2, "Channel %s is not a DAHDI channel\n", ast_channel_name(chan));
-		sleep(2);
-	} else {
-		memset(&dahdip, 0, sizeof(dahdip));
-		if (ioctl(ast_channel_fd(chan, 0), DAHDI_GET_PARAMS, &dahdip)) {
-			ast_log(LOG_WARNING, "Unable to get DAHDI parameters\n");
-		} else if (dahdip.sigtype != DAHDI_SIG_CLEAR) {
-			ast_verb(2, "Channel %s is not a clear channel\n", ast_channel_name(chan));
-		} else {
-			/* Everything should be okay.  Run PPP. */
-			ast_verb(3, "Starting RAS on %s\n", ast_channel_name(chan));
-			/* Execute RAS */
-			run_ras(chan, args);
-		}
-	}
-	return res;
-}
-
-static int unload_module(void)
-{
-	return ast_unregister_application(app);
-}
-
-static int load_module(void)
-{
-	return ((ast_register_application_xml(app, dahdiras_exec)) ? AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS);
-}
-
-AST_MODULE_INFO_STANDARD_DEPRECATED(ASTERISK_GPL_KEY, "DAHDI ISDN Remote Access Server");
diff --git a/apps/app_dial.c b/apps/app_dial.c
index f4aa946b1efdd8992396a19daba8a2b638b626d1..8a3c70e0b7adc943fcecfbc56db86be72f9c3b13 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -86,6 +86,7 @@
 					<para>If you need more than one enter them as
 					Technology2/Resource2&amp;Technology3/Resource3&amp;.....</para>
 				</argument>
+				<xi:include xpointer="xpointer(/docs/info[@name='Dial_Resource'])" />
 			</parameter>
 			<parameter name="timeout" required="false">
 				<para>Specifies the number of seconds we attempt to dial the specified devices.</para>
@@ -371,7 +372,7 @@
 					</argument>
 					<para>Enables <emphasis>operator services</emphasis> mode.  This option only
 					works when bridging a DAHDI channel to another DAHDI channel
-					only. if specified on non-DAHDI interfaces, it will be ignored.
+					only. If specified on non-DAHDI interfaces, it will be ignored.
 					When the destination answers (presumably an operator services
 					station), the originator no longer has control of their line.
 					They may hang up, but the switch will not release their line
@@ -613,12 +614,31 @@
 				</variable>
 				<variable name="DIALSTATUS">
 					<para>This is the status of the call</para>
-					<value name="CHANUNAVAIL" />
-					<value name="CONGESTION" />
-					<value name="NOANSWER" />
-					<value name="BUSY" />
-					<value name="ANSWER" />
-					<value name="CANCEL" />
+					<value name="CHANUNAVAIL">
+						Either the dialed peer exists but is not currently reachable, e.g.
+						endpoint is not registered, or an attempt was made to call a
+						nonexistent location, e.g. nonexistent DNS hostname.
+					</value>
+					<value name="CONGESTION">
+						Channel or switching congestion occured when routing the call.
+						This can occur if there is a slow or no response from the remote end.
+					</value>
+					<value name="NOANSWER">
+						Called party did not answer.
+					</value>
+					<value name="BUSY">
+						The called party was busy or indicated a busy status.
+						Note that some SIP devices will respond with 486 Busy if their Do Not Disturb
+						modes are active. In this case, you can use DEVICE_STATUS to check if the
+						endpoint is actually in use, if needed.
+					</value>
+					<value name="ANSWER">
+						The call was answered.
+						Any other result implicitly indicates the call was not answered.
+					</value>
+					<value name="CANCEL">
+						Dial was cancelled before call was answered or reached some other terminating event.
+					</value>
 					<value name="DONTCALL">
 						For the Privacy and Screening Modes.
 						Will be set if the called party chooses to send the calling party to the 'Go Away' script.
@@ -627,7 +647,9 @@
 						For the Privacy and Screening Modes.
 						Will be set if the called party chooses to send the calling party to the 'torture' script.
 					</value>
-					<value name="INVALIDARGS" />
+					<value name="INVALIDARGS">
+						Dial failed due to invalid syntax.
+					</value>
 				</variable>
 			</variablelist>
 		</description>
@@ -1303,7 +1325,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 			if (is_cc_recall) {
 				ast_cc_failed(cc_recall_core_id, "Everyone is busy/congested for the recall. How sad");
 			}
-			SCOPE_EXIT_RTN_VALUE(NULL, "%s: No outging channels available\n", ast_channel_name(in));
+			SCOPE_EXIT_RTN_VALUE(NULL, "%s: No outgoing channels available\n", ast_channel_name(in));
 		}
 		winner = ast_waitfor_n(watchers, pos, to);
 		AST_LIST_TRAVERSE(out_chans, o, node) {
@@ -1744,6 +1766,8 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 			case AST_FRAME_VIDEO:
 			case AST_FRAME_VOICE:
 			case AST_FRAME_IMAGE:
+			case AST_FRAME_DTMF_BEGIN:
+			case AST_FRAME_DTMF_END:
 				if (caller_entertained) {
 					break;
 				}
@@ -1875,6 +1899,10 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
 						ast_verb(3, "Call on %s left from hold\n", ast_channel_name(o->chan));
 						ast_indicate(o->chan, AST_CONTROL_UNHOLD);
 						break;
+					case AST_CONTROL_FLASH:
+						ast_verb(3, "Hook flash on %s\n", ast_channel_name(o->chan));
+						ast_indicate(o->chan, AST_CONTROL_FLASH);
+						break;
 					case AST_CONTROL_VIDUPDATE:
 					case AST_CONTROL_SRCUPDATE:
 					case AST_CONTROL_SRCCHANGE:
@@ -2590,9 +2618,11 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
 		struct ast_channel *tc; /* channel for this destination */
 		char *number;
 		char *tech;
+		int i;
 		size_t tech_len;
 		size_t number_len;
 		struct ast_stream_topology *topology;
+		struct ast_stream *stream;
 
 		cur = ast_strip(cur);
 		if (ast_strlen_zero(cur)) {
@@ -2658,13 +2688,29 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
 
 		ast_channel_unlock(chan);
 
+		for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {
+			stream = ast_stream_topology_get_stream(topology, i);
+			/* For both recvonly and sendonly the stream state reflects our state, that is we
+			 * are receiving only and we are sending only. Since we are requesting a
+			 * channel for the peer, we need to swap this to reflect what we will be doing.
+			 * That is, if we are receiving from Alice then we want to be sending to Bob,
+			 * so swap recvonly to sendonly and vice versa.
+			 */
+			if (ast_stream_get_state(stream) == AST_STREAM_STATE_RECVONLY) {
+				ast_stream_set_state(stream, AST_STREAM_STATE_SENDONLY);
+			} else if (ast_stream_get_state(stream) == AST_STREAM_STATE_SENDONLY) {
+				ast_stream_set_state(stream, AST_STREAM_STATE_RECVONLY);
+			}
+		}
+
 		tc = ast_request_with_stream_topology(tmp->tech, topology, NULL, chan, tmp->number, &cause);
 
 		ast_stream_topology_free(topology);
 
 		if (!tc) {
 			/* If we can't, just go on to the next call */
-			ast_log(LOG_WARNING, "Unable to create channel of type '%s' (cause %d - %s)\n",
+			/* Failure doesn't necessarily mean user error. DAHDI channels could be busy. */
+			ast_log(LOG_NOTICE, "Unable to create channel of type '%s' (cause %d - %s)\n",
 				tmp->tech, cause, ast_cause2str(cause));
 			handle_cause(cause, &num);
 			if (!rest) {
@@ -2802,7 +2848,9 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
 		AST_LIST_INSERT_TAIL(&out_chans, tmp, node);
 	}
 
-	if (AST_LIST_EMPTY(&out_chans)) {
+	/* As long as we attempted to dial valid peers, don't throw a warning. */
+	/* If a DAHDI peer is busy, out_chans will be empty so checking list size is misleading. */
+	if (!num_dialed) {
 		ast_verb(3, "No devices or endpoints to dial (technology/resource)\n");
 		if (continue_exec) {
 			/* There is no point in having RetryDial try again */
@@ -3606,4 +3654,4 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Dialing Application",
 	.load = load_module,
 	.unload = unload_module,
 	.requires = "ccss",
-);
\ No newline at end of file
+);
diff --git a/apps/app_directory.c b/apps/app_directory.c
index 36da702a5e51a3d7e99e6ce258f5d9c0439ce603..40f78966cd618d46d78f0ccaf841dc4974622e03 100644
--- a/apps/app_directory.c
+++ b/apps/app_directory.c
@@ -103,6 +103,14 @@
 						receiver to their ear while entering DTMF.</para>
 						<argument name="n" required="true" />
 					</option>
+					<option name="c">
+						<para>Load the specified config file instead of voicemail.conf</para>
+						<argument name="filename" required="true" />
+					</option>
+					<option name="s">
+						<para>Skip calling the extension, instead set it in the <variable>DIRECTORY_EXTEN</variable>
+						channel variable.</para>
+					</option>
 				</optionlist>
 				<note><para>Only one of the <replaceable>f</replaceable>, <replaceable>l</replaceable>, or <replaceable>b</replaceable>
 				options may be specified. <emphasis>If more than one is specified</emphasis>, then Directory will act as
@@ -114,12 +122,12 @@
 		<description>
 			<para>This application will present the calling channel with a directory of extensions from which they can search
 			by name. The list of names and corresponding extensions is retrieved from the
-			voicemail configuration file, <filename>voicemail.conf</filename>.</para>
+			voicemail configuration file, <filename>voicemail.conf</filename>, or from the specified filename.</para>
 			<para>This application will immediately exit if one of the following DTMF digits are
 			received and the extension to jump to exists:</para>
 			<para><literal>0</literal> - Jump to the 'o' extension, if it exists.</para>
 			<para><literal>*</literal> - Jump to the 'a' extension, if it exists.</para>
-			<para>This application will set the following channel variable before completion:</para>
+			<para>This application will set the following channel variables before completion:</para>
 			<variablelist>
 				<variable name="DIRECTORY_RESULT">
 					<para>Reason Directory application exited.</para>
@@ -131,6 +139,10 @@
 					<value name="USEREXIT">User exited with '#' during selection</value>
 					<value name="FAILED">The application failed</value>
 				</variable>
+				<variable name="DIRECTORY_EXTEN">
+					<para>If the skip calling option is set this will be set to the selected extension
+					provided one is selected.</para>
+				</variable>
 			</variablelist>
 		</description>
 	</application>
@@ -153,6 +165,8 @@ enum {
 	OPT_PAUSE =           (1 << 5),
 	OPT_NOANSWER =        (1 << 6),
 	OPT_ALIAS =           (1 << 7),
+	OPT_CONFIG_FILE =     (1 << 8),
+	OPT_SKIP =            (1 << 9),
 };
 
 enum {
@@ -160,8 +174,9 @@ enum {
 	OPT_ARG_LASTNAME =    1,
 	OPT_ARG_EITHER =      2,
 	OPT_ARG_PAUSE =       3,
+	OPT_ARG_FILENAME =    4,
 	/* This *must* be the last value in this enum! */
-	OPT_ARG_ARRAY_SIZE =  4,
+	OPT_ARG_ARRAY_SIZE =  5,
 };
 
 struct directory_item {
@@ -183,6 +198,8 @@ AST_APP_OPTIONS(directory_app_options, {
 	AST_APP_OPTION('m', OPT_SELECTFROMMENU),
 	AST_APP_OPTION('n', OPT_NOANSWER),
 	AST_APP_OPTION('a', OPT_ALIAS),
+	AST_APP_OPTION_ARG('c', OPT_CONFIG_FILE, OPT_ARG_FILENAME),
+	AST_APP_OPTION('s', OPT_SKIP),
 });
 
 static int compare(const char *text, const char *template)
@@ -318,6 +335,9 @@ static int select_entry(struct ast_channel *chan, const char *dialcontext, const
 	if (ast_test_flag(flags, OPT_FROMVOICEMAIL)) {
 		/* We still want to set the exten though */
 		ast_channel_exten_set(chan, item->exten);
+	} else if (ast_test_flag(flags, OPT_SKIP)) {
+		/* Skip calling the extension, only set it in the channel variable. */
+		pbx_builtin_setvar_helper(chan, "DIRECTORY_EXTEN", item->exten);
 	} else if (ast_goto_if_exists(chan, S_OR(dialcontext, item->context), item->exten, 1)) {
 		ast_log(LOG_WARNING,
 			"Can't find extension '%s' in context '%s'.  "
@@ -458,7 +478,7 @@ static int select_item_menu(struct ast_channel *chan, struct directory_item **it
 
 AST_THREADSTORAGE(commonbuf);
 
-static struct ast_config *realtime_directory(char *context)
+static struct ast_config *realtime_directory(char *context, const char *filename)
 {
 	struct ast_config *cfg;
 	struct ast_config *rtdata = NULL;
@@ -475,14 +495,14 @@ static struct ast_config *realtime_directory(char *context)
 	}
 
 	/* Load flat file config. */
-	cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
+	cfg = ast_config_load(filename, config_flags);
 
 	if (!cfg) {
 		/* Loading config failed. */
 		ast_log(LOG_WARNING, "Loading config failed.\n");
 		return NULL;
 	} else if (cfg == CONFIG_STATUS_FILEINVALID) {
-		ast_log(LOG_ERROR, "Config file %s is in an invalid format.  Aborting.\n", VOICEMAIL_CONFIG);
+		ast_log(LOG_ERROR, "Config file %s is in an invalid format.  Aborting.\n", filename);
 		return NULL;
 	}
 
@@ -867,7 +887,9 @@ static int directory_exec(struct ast_channel *chan, const char *data)
 	if (args.options && ast_app_parse_options(directory_app_options, &flags, opts, args.options))
 		return -1;
 
-	if (!(cfg = realtime_directory(args.vmcontext))) {
+	cfg = realtime_directory(args.vmcontext, S_OR(opts[OPT_ARG_FILENAME], VOICEMAIL_CONFIG));
+
+	if (!cfg) {
 		ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
 		return -1;
 	}
diff --git a/apps/app_disa.c b/apps/app_disa.c
index cceb5541dda83356f366c1e3141844e3d54c8481..44cccc7a6aa2682bc702f8558a87db71558698c5 100644
--- a/apps/app_disa.c
+++ b/apps/app_disa.c
@@ -361,7 +361,6 @@ static int disa_exec(struct ast_channel *chan, const char *data)
 
 	if (k == 3) {
 		int recheck = 0;
-		struct ast_app *app_reset_cdr;
 
 		if (!ast_exists_extension(chan, args.context, exten, 1,
 			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
@@ -386,10 +385,7 @@ static int disa_exec(struct ast_channel *chan, const char *data)
 				ast_channel_unlock(chan);
 			}
 
-			app_reset_cdr = pbx_findapp("ResetCDR");
-			if (app_reset_cdr) {
-				pbx_exec(chan, app_reset_cdr, special_noanswer ? "" : "e");
-			} else {
+			if (ast_pbx_exec_application(chan, "ResetCDR", special_noanswer ? "" : "e")) {
 				ast_log(AST_LOG_NOTICE, "ResetCDR application not found; CDR will not be reset\n");
 			}
 			ast_explicit_goto(chan, args.context, exten, 1);
diff --git a/apps/app_dtmfstore.c b/apps/app_dtmfstore.c
index 641170f1934ac6c8517deaac247d61e8774a1646..a2dacd54f7a1100857fa6878ec62a947f512721e 100644
--- a/apps/app_dtmfstore.c
+++ b/apps/app_dtmfstore.c
@@ -41,6 +41,11 @@
 
 /*** DOCUMENTATION
 	<application name="StoreDTMF" language="en_US">
+		<since>
+			<version>16.20.0</version>
+			<version>18.6.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Stores DTMF digits transmitted or received on a channel.
 		</synopsis>
diff --git a/apps/app_fax.c b/apps/app_fax.c
deleted file mode 100644
index 442df6666b6e8f70f7325d6e7d089fb19ca1a18f..0000000000000000000000000000000000000000
--- a/apps/app_fax.c
+++ /dev/null
@@ -1,1005 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Simple fax applications
- *
- * 2007-2008, Dmitry Andrianov <asterisk@dima.spb.ru>
- *
- * Code based on original implementation by Steve Underwood <steveu@coppice.org>
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License
- *
- */
-
-/*** MODULEINFO
-	<depend>spandsp</depend>
-	<conflict>res_fax</conflict>
-	<defaultenabled>no</defaultenabled>
-	<support_level>deprecated</support_level>
-	<replacement>res_fax</replacement>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
-***/
-
-/* Needed for spandsp headers */
-#define ASTMM_LIBC ASTMM_IGNORE
-#include "asterisk.h"
-
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <inttypes.h>
-#include <pthread.h>
-#include <errno.h>
-#include <tiffio.h>
-
-#define SPANDSP_EXPOSE_INTERNAL_STRUCTURES
-#include <spandsp.h>
-#include <spandsp/version.h>
-
-#include "asterisk/lock.h"
-#include "asterisk/file.h"
-#include "asterisk/logger.h"
-#include "asterisk/channel.h"
-#include "asterisk/pbx.h"
-#include "asterisk/app.h"
-#include "asterisk/dsp.h"
-#include "asterisk/module.h"
-#include "asterisk/stasis.h"
-#include "asterisk/stasis_channels.h"
-#include "asterisk/format_cache.h"
-
-/*** DOCUMENTATION
-	<application name="SendFAX" language="en_US" module="app_fax">
-		<synopsis>
-			Send a Fax
-		</synopsis>
-		<syntax>
-			<parameter name="filename" required="true">
-				<para>Filename of TIFF file to fax</para>
-			</parameter>
-			<parameter name="a" required="false">
-				<para>Makes the application behave as the answering machine</para>
-				<para>(Default behavior is as calling machine)</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>Send a given TIFF file to the channel as a FAX.</para>
-			<para>This application sets the following channel variables:</para>
-			<variablelist>
-				<variable name="LOCALSTATIONID">
-					<para>To identify itself to the remote end</para>
-				</variable>
-				<variable name="LOCALHEADERINFO">
-					<para>To generate a header line on each page</para>
-				</variable>
-				<variable name="FAXSTATUS">
-					<value name="SUCCESS"/>
-					<value name="FAILED"/>
-				</variable>
-				<variable name="FAXERROR">
-					<para>Cause of failure</para>
-				</variable>
-				<variable name="REMOTESTATIONID">
-					<para>The CSID of the remote side</para>
-				</variable>
-				<variable name="FAXPAGES">
-					<para>Number of pages sent</para>
-				</variable>
-				<variable name="FAXBITRATE">
-					<para>Transmission rate</para>
-				</variable>
-				<variable name="FAXRESOLUTION">
-					<para>Resolution of sent fax</para>
-				</variable>
-			</variablelist>
-		</description>
-	</application>
-	<application name="ReceiveFAX" language="en_US" module="app_fax">
-		<synopsis>
-			Receive a Fax
-		</synopsis>
-		<syntax>
-			<parameter name="filename" required="true">
-				<para>Filename of TIFF file save incoming fax</para>
-			</parameter>
-			<parameter name="c" required="false">
-				<para>Makes the application behave as the calling machine</para>
-				<para>(Default behavior is as answering machine)</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>Receives a FAX from the channel into the given filename
-			overwriting the file if it already exists.</para>
-			<para>File created will be in TIFF format.</para>
-
-			<para>This application sets the following channel variables:</para>
-			<variablelist>
-				<variable name="LOCALSTATIONID">
-					<para>To identify itself to the remote end</para>
-				</variable>
-				<variable name="LOCALHEADERINFO">
-					<para>To generate a header line on each page</para>
-				</variable>
-				<variable name="FAXSTATUS">
-					<value name="SUCCESS"/>
-					<value name="FAILED"/>
-				</variable>
-				<variable name="FAXERROR">
-					<para>Cause of failure</para>
-				</variable>
-				<variable name="REMOTESTATIONID">
-					<para>The CSID of the remote side</para>
-				</variable>
-				<variable name="FAXPAGES">
-					<para>Number of pages sent</para>
-				</variable>
-				<variable name="FAXBITRATE">
-					<para>Transmission rate</para>
-				</variable>
-				<variable name="FAXRESOLUTION">
-					<para>Resolution of sent fax</para>
-				</variable>
-			</variablelist>
-		</description>
-	</application>
-
- ***/
-
-static const char app_sndfax_name[] = "SendFAX";
-static const char app_rcvfax_name[] = "ReceiveFAX";
-
-#define MAX_SAMPLES 240
-
-/* Watchdog. I have seen situations when remote fax disconnects (because of poor line
-   quality) while SpanDSP continues staying in T30_STATE_IV_CTC state forever.
-   To avoid this, we terminate when we see that T30 state does not change for 5 minutes.
-   We also terminate application when more than 30 minutes passed regardless of
-   state changes. This is just a precaution measure - no fax should take that long */
-
-#define WATCHDOG_TOTAL_TIMEOUT	30 * 60
-#define WATCHDOG_STATE_TIMEOUT	5 * 60
-
-typedef struct {
-	struct ast_channel *chan;
-	enum ast_t38_state t38state;	/* T38 state of the channel */
-	int direction;			/* Fax direction: 0 - receiving, 1 - sending */
-	int caller_mode;
-	char *file_name;
-	struct ast_control_t38_parameters t38parameters;
-	volatile int finished;
-} fax_session;
-
-static void span_message(int level, const char *msg)
-{
-	if (level == SPAN_LOG_ERROR) {
-		ast_log(LOG_ERROR, "%s", msg);
-	} else if (level == SPAN_LOG_WARNING) {
-		ast_log(LOG_WARNING, "%s", msg);
-	} else {
-		ast_debug(1, "%s", msg);
-	}
-}
-
-static int t38_tx_packet_handler(t38_core_state_t *s, void *user_data, const uint8_t *buf, int len, int count)
-{
-	struct ast_channel *chan = (struct ast_channel *) user_data;
-
-	struct ast_frame outf = {
-		.frametype = AST_FRAME_MODEM,
-		.subclass.integer = AST_MODEM_T38,
-		.src = __FUNCTION__,
-	};
-
-	/* TODO: Asterisk does not provide means of resending the same packet multiple
-	  times so count is ignored at the moment */
-
-	AST_FRAME_SET_BUFFER(&outf, buf, 0, len);
-
-	if (ast_write(chan, &outf) < 0) {
-		ast_log(LOG_WARNING, "Unable to write frame to channel; %s\n", strerror(errno));
-		return -1;
-	}
-
-	return 0;
-}
-
-static void phase_e_handler(t30_state_t *f, void *user_data, int result)
-{
-	RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref);
-	RAII_VAR(struct ast_json *, json_filenames, NULL, ast_json_unref);
-	RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
-	const char *local_ident;
-	const char *far_ident;
-	char buf[20];
-	fax_session *s = (fax_session *) user_data;
-	t30_stats_t stat;
-	int pages_transferred;
-
-	ast_debug(1, "Fax phase E handler. result=%d\n", result);
-
-	t30_get_transfer_statistics(f, &stat);
-
-	s = (fax_session *) user_data;
-
-	if (result != T30_ERR_OK) {
-		s->finished = -1;
-
-		/* FAXSTATUS is already set to FAILED */
-		pbx_builtin_setvar_helper(s->chan, "FAXERROR", t30_completion_code_to_str(result));
-
-		ast_log(LOG_WARNING, "Error transmitting fax. result=%d: %s.\n", result, t30_completion_code_to_str(result));
-
-		return;
-	}
-
-	s->finished = 1;
-
-	local_ident = S_OR(t30_get_tx_ident(f), "");
-	far_ident = S_OR(t30_get_rx_ident(f), "");
-	pbx_builtin_setvar_helper(s->chan, "FAXSTATUS", "SUCCESS");
-	pbx_builtin_setvar_helper(s->chan, "FAXERROR", NULL);
-	pbx_builtin_setvar_helper(s->chan, "REMOTESTATIONID", far_ident);
-#if SPANDSP_RELEASE_DATE >= 20090220
-	pages_transferred = (s->direction) ? stat.pages_tx : stat.pages_rx;
-#else
-	pages_transferred = stat.pages_transferred;
-#endif
-	snprintf(buf, sizeof(buf), "%d", pages_transferred);
-	pbx_builtin_setvar_helper(s->chan, "FAXPAGES", buf);
-	snprintf(buf, sizeof(buf), "%d", stat.y_resolution);
-	pbx_builtin_setvar_helper(s->chan, "FAXRESOLUTION", buf);
-	snprintf(buf, sizeof(buf), "%d", stat.bit_rate);
-	pbx_builtin_setvar_helper(s->chan, "FAXBITRATE", buf);
-
-	ast_debug(1, "Fax transmitted successfully.\n");
-	ast_debug(1, "  Remote station ID: %s\n", far_ident);
-	ast_debug(1, "  Pages transferred: %d\n", pages_transferred);
-	ast_debug(1, "  Image resolution:  %d x %d\n", stat.x_resolution, stat.y_resolution);
-	ast_debug(1, "  Transfer Rate:     %d\n", stat.bit_rate);
-
-	json_filenames = ast_json_pack("[s]", s->file_name);
-	if (!json_filenames) {
-		return;
-	}
-	ast_json_ref(json_filenames);
-	json_object = ast_json_pack("{s: s, s: s, s: s, s: i, s: i, s: i, s: o}",
-		"type", s->direction ? "send" : "receive",
-		"remote_station_id", AST_JSON_UTF8_VALIDATE(far_ident),
-		"local_station_id", AST_JSON_UTF8_VALIDATE(local_ident),
-		"fax_pages", pages_transferred,
-		"fax_resolution", stat.y_resolution,
-		"fax_bitrate", stat.bit_rate,
-		"filenames", json_filenames);
-	message = ast_channel_blob_create_from_cache(ast_channel_uniqueid(s->chan), ast_channel_fax_type(), json_object);
-	if (!message) {
-		return;
-	}
-	stasis_publish(ast_channel_topic(s->chan), message);
-}
-
-/* === Helper functions to configure fax === */
-
-/* Setup SPAN logging according to Asterisk debug level */
-static int set_logging(logging_state_t *state)
-{
-	int level = SPAN_LOG_WARNING + option_debug;
-
-	span_log_set_message_handler(state, span_message);
-	span_log_set_level(state, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | level);
-
-	return 0;
-}
-
-static void set_local_info(t30_state_t *state, fax_session *s)
-{
-	const char *x;
-
-	x = pbx_builtin_getvar_helper(s->chan, "LOCALSTATIONID");
-	if (!ast_strlen_zero(x))
-		t30_set_tx_ident(state, x);
-
-	x = pbx_builtin_getvar_helper(s->chan, "LOCALHEADERINFO");
-	if (!ast_strlen_zero(x))
-		t30_set_tx_page_header_info(state, x);
-}
-
-static void set_file(t30_state_t *state, fax_session *s)
-{
-	if (s->direction)
-		t30_set_tx_file(state, s->file_name, -1, -1);
-	else
-		t30_set_rx_file(state, s->file_name, -1);
-}
-
-static void set_ecm(t30_state_t *state, int ecm)
-{
-	t30_set_ecm_capability(state, ecm);
-	t30_set_supported_compressions(state, T30_SUPPORT_T4_1D_COMPRESSION | T30_SUPPORT_T4_2D_COMPRESSION | T30_SUPPORT_T6_COMPRESSION);
-}
-
-/* === Generator === */
-
-/* This function is only needed to return passed params so
-   generator_activate will save it to channel's generatordata */
-static void *fax_generator_alloc(struct ast_channel *chan, void *params)
-{
-	return params;
-}
-
-static int fax_generator_generate(struct ast_channel *chan, void *data, int len, int samples)
-{
-	fax_state_t *fax = (fax_state_t*) data;
-	uint8_t buffer[AST_FRIENDLY_OFFSET + MAX_SAMPLES * sizeof(uint16_t)];
-	int16_t *buf = (int16_t *) (buffer + AST_FRIENDLY_OFFSET);
-
-	struct ast_frame outf = {
-		.frametype = AST_FRAME_VOICE,
-		.subclass.format = ast_format_slin,
-		.src = __FUNCTION__,
-	};
-
-	if (samples > MAX_SAMPLES) {
-		ast_log(LOG_WARNING, "Only generating %d samples, where %d requested\n", MAX_SAMPLES, samples);
-		samples = MAX_SAMPLES;
-	}
-
-	if ((len = fax_tx(fax, buf, samples)) > 0) {
-		outf.samples = len;
-		AST_FRAME_SET_BUFFER(&outf, buffer, AST_FRIENDLY_OFFSET, len * sizeof(int16_t));
-
-		if (ast_write(chan, &outf) < 0) {
-			ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", ast_channel_name(chan), strerror(errno));
-			return -1;
-		}
-	}
-
-	return 0;
-}
-
-static struct ast_generator generator = {
-	.alloc = fax_generator_alloc,
-	.generate = fax_generator_generate,
-};
-
-
-/* === Transmission === */
-
-static int transmit_audio(fax_session *s)
-{
-	int res = -1;
-	struct ast_format *original_read_fmt;
-	struct ast_format *original_write_fmt = NULL;
-	fax_state_t fax;
-	t30_state_t *t30state;
-	struct ast_frame *inf = NULL;
-	int last_state = 0;
-	struct timeval now, start, state_change;
-	enum ast_t38_state t38_state;
-	struct ast_control_t38_parameters t38_parameters = { .version = 0,
-							     .max_ifp = 800,
-							     .rate = AST_T38_RATE_14400,
-							     .rate_management = AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF,
-							     .fill_bit_removal = 1,
-/*
- * spandsp has API calls to support MMR and JBIG transcoding, but they aren't
- * implemented quite yet... so don't offer them to the remote endpoint
- *							     .transcoding_mmr = 1,
- *							     .transcoding_jbig = 1,
-*/
-	};
-
-	/* if in called party mode, try to use T.38 */
-	if (s->caller_mode == FALSE) {
-		/* check if we are already in T.38 mode (unlikely), or if we can request
-		 * a switch... if so, request it now and wait for the result, rather
-		 * than starting an audio FAX session that will have to be cancelled
-		 */
-		if ((t38_state = ast_channel_get_t38_state(s->chan)) == T38_STATE_NEGOTIATED) {
-			return 1;
-		} else if ((t38_state != T38_STATE_UNAVAILABLE) &&
-			   (t38_parameters.request_response = AST_T38_REQUEST_NEGOTIATE,
-			    (ast_indicate_data(s->chan, AST_CONTROL_T38_PARAMETERS, &t38_parameters, sizeof(t38_parameters)) == 0))) {
-			/* wait up to five seconds for negotiation to complete */
-			unsigned int timeout = 5000;
-			int ms;
-
-			ast_debug(1, "Negotiating T.38 for receive on %s\n", ast_channel_name(s->chan));
-			while (timeout > 0) {
-				ms = ast_waitfor(s->chan, 1000);
-				if (ms < 0) {
-					ast_log(LOG_WARNING, "something bad happened while channel '%s' was polling.\n", ast_channel_name(s->chan));
-					return -1;
-				}
-				if (!ms) {
-					/* nothing happened */
-					if (timeout > 0) {
-						timeout -= 1000;
-						continue;
-					} else {
-						ast_log(LOG_WARNING, "channel '%s' timed-out during the T.38 negotiation.\n", ast_channel_name(s->chan));
-						break;
-					}
-				}
-				if (!(inf = ast_read(s->chan))) {
-					return -1;
-				}
-				if ((inf->frametype == AST_FRAME_CONTROL) &&
-				    (inf->subclass.integer == AST_CONTROL_T38_PARAMETERS) &&
-				    (inf->datalen == sizeof(t38_parameters))) {
-					struct ast_control_t38_parameters *parameters = inf->data.ptr;
-
-					switch (parameters->request_response) {
-					case AST_T38_NEGOTIATED:
-						ast_debug(1, "Negotiated T.38 for receive on %s\n", ast_channel_name(s->chan));
-						res = 1;
-						break;
-					case AST_T38_REFUSED:
-						ast_log(LOG_WARNING, "channel '%s' refused to negotiate T.38\n", ast_channel_name(s->chan));
-						break;
-					default:
-						ast_log(LOG_ERROR, "channel '%s' failed to negotiate T.38\n", ast_channel_name(s->chan));
-						break;
-					}
-					ast_frfree(inf);
-					if (res == 1) {
-						return 1;
-					} else {
-						break;
-					}
-				}
-				ast_frfree(inf);
-			}
-		}
-	}
-
-#if SPANDSP_RELEASE_DATE >= 20080725
-        /* for spandsp shaphots 0.0.6 and higher */
-        t30state = &fax.t30;
-#else
-        /* for spandsp release 0.0.5 */
-        t30state = &fax.t30_state;
-#endif
-
-    original_read_fmt = ao2_bump(ast_channel_readformat(s->chan));
-	res = ast_set_read_format(s->chan, ast_format_slin);
-	if (res < 0) {
-		ast_log(LOG_WARNING, "Unable to set to linear read mode, giving up\n");
-		goto done;
-	}
-
-	original_write_fmt = ao2_bump(ast_channel_writeformat(s->chan));
-	res = ast_set_write_format(s->chan, ast_format_slin);
-	if (res < 0) {
-		ast_log(LOG_WARNING, "Unable to set to linear write mode, giving up\n");
-		goto done;
-	}
-
-	/* Initialize T30 terminal */
-	fax_init(&fax, s->caller_mode);
-
-	/* Setup logging */
-	set_logging(&fax.logging);
-	set_logging(&t30state->logging);
-
-	/* Configure terminal */
-	set_local_info(t30state, s);
-	set_file(t30state, s);
-	set_ecm(t30state, TRUE);
-
-	fax_set_transmit_on_idle(&fax, TRUE);
-
-	t30_set_phase_e_handler(t30state, phase_e_handler, s);
-
-	start = state_change = ast_tvnow();
-
-	ast_activate_generator(s->chan, &generator, &fax);
-
-	while (!s->finished) {
-		inf = NULL;
-
-		if ((res = ast_waitfor(s->chan, 25)) < 0) {
-			ast_debug(1, "Error waiting for a frame\n");
-			break;
-		}
-
-		/* Watchdog */
-		now = ast_tvnow();
-		if (ast_tvdiff_sec(now, start) > WATCHDOG_TOTAL_TIMEOUT || ast_tvdiff_sec(now, state_change) > WATCHDOG_STATE_TIMEOUT) {
-			ast_log(LOG_WARNING, "It looks like we hung. Aborting.\n");
-			res = -1;
-			break;
-		}
-
-		if (!res) {
-			/* There was timeout waiting for a frame. Loop around and wait again */
-			continue;
-		}
-
-		/* There is a frame available. Get it */
-		res = 0;
-
-		if (!(inf = ast_read(s->chan))) {
-			ast_debug(1, "Channel hangup\n");
-			res = -1;
-			break;
-		}
-
-		ast_debug(10, "frame %d/%s, len=%d\n", inf->frametype, ast_format_get_name(inf->subclass.format), inf->datalen);
-
-		/* Check the frame type. Format also must be checked because there is a chance
-		   that a frame in old format was already queued before we set channel format
-		   to slinear so it will still be received by ast_read */
-		if (inf->frametype == AST_FRAME_VOICE &&
-			(ast_format_cmp(inf->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL)) {
-			if (fax_rx(&fax, inf->data.ptr, inf->samples) < 0) {
-				/* I know fax_rx never returns errors. The check here is for good style only */
-				ast_log(LOG_WARNING, "fax_rx returned error\n");
-				res = -1;
-				break;
-			}
-			if (last_state != t30state->state) {
-				state_change = ast_tvnow();
-				last_state = t30state->state;
-			}
-		} else if ((inf->frametype == AST_FRAME_CONTROL) &&
-			   (inf->subclass.integer == AST_CONTROL_T38_PARAMETERS)) {
-			struct ast_control_t38_parameters *parameters = inf->data.ptr;
-
-			if (parameters->request_response == AST_T38_NEGOTIATED) {
-				/* T38 switchover completed */
-				s->t38parameters = *parameters;
-				ast_debug(1, "T38 negotiated, finishing audio loop\n");
-				res = 1;
-				break;
-			} else if (parameters->request_response == AST_T38_REQUEST_NEGOTIATE) {
-				t38_parameters.request_response = AST_T38_NEGOTIATED;
-				ast_debug(1, "T38 request received, accepting\n");
-				/* Complete T38 switchover */
-				ast_indicate_data(s->chan, AST_CONTROL_T38_PARAMETERS, &t38_parameters, sizeof(t38_parameters));
-				/* Do not break audio loop, wait until channel driver finally acks switchover
-				 * with AST_T38_NEGOTIATED
-				 */
-			}
-		}
-
-		ast_frfree(inf);
-		inf = NULL;
-	}
-
-	ast_debug(1, "Loop finished, res=%d\n", res);
-
-	if (inf)
-		ast_frfree(inf);
-
-	ast_deactivate_generator(s->chan);
-
-	/* If we are switching to T38, remove phase E handler. Otherwise it will be executed
-	   by t30_terminate, display diagnostics and set status variables although no transmittion
-	   has taken place yet. */
-	if (res > 0) {
-		t30_set_phase_e_handler(t30state, NULL, NULL);
-	}
-
-	t30_terminate(t30state);
-	fax_release(&fax);
-
-done:
-	if (original_write_fmt) {
-		if (ast_set_write_format(s->chan, original_write_fmt) < 0)
-			ast_log(LOG_WARNING, "Unable to restore write format on '%s'\n", ast_channel_name(s->chan));
-		ao2_ref(original_write_fmt, -1);
-	}
-
-	if (original_read_fmt) {
-		if (ast_set_read_format(s->chan, original_read_fmt) < 0)
-			ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", ast_channel_name(s->chan));
-		ao2_ref(original_read_fmt, -1);
-	}
-
-	return res;
-
-}
-
-static int transmit_t38(fax_session *s)
-{
-	int res = 0;
-	t38_terminal_state_t t38;
-	struct ast_frame *inf = NULL;
-	int last_state = 0;
-	struct timeval now, start, state_change, last_frame;
-	t30_state_t *t30state;
-	t38_core_state_t *t38state;
-
-#if SPANDSP_RELEASE_DATE >= 20080725
-	/* for spandsp shaphots 0.0.6 and higher */
-	t30state = &t38.t30;
-	t38state = &t38.t38_fe.t38;
-#else
-	/* for spandsp releases 0.0.5 */
-	t30state = &t38.t30_state;
-	t38state = &t38.t38;
-#endif
-
-	/* Initialize terminal */
-	memset(&t38, 0, sizeof(t38));
-	if (t38_terminal_init(&t38, s->caller_mode, t38_tx_packet_handler, s->chan) == NULL) {
-		ast_log(LOG_WARNING, "Unable to start T.38 termination.\n");
-		res = -1;
-		goto disable_t38;
-	}
-
-	t38_set_max_datagram_size(t38state, s->t38parameters.max_ifp);
-
-	if (s->t38parameters.fill_bit_removal) {
-		t38_set_fill_bit_removal(t38state, TRUE);
-	}
-	if (s->t38parameters.transcoding_mmr) {
-		t38_set_mmr_transcoding(t38state, TRUE);
-	}
-	if (s->t38parameters.transcoding_jbig) {
-		t38_set_jbig_transcoding(t38state, TRUE);
-	}
-
-	/* Setup logging */
-	set_logging(&t38.logging);
-	set_logging(&t30state->logging);
-	set_logging(&t38state->logging);
-
-	/* Configure terminal */
-	set_local_info(t30state, s);
-	set_file(t30state, s);
-	set_ecm(t30state, TRUE);
-
-	t30_set_phase_e_handler(t30state, phase_e_handler, s);
-
-	now = start = state_change = ast_tvnow();
-
-	while (!s->finished) {
-		inf = NULL;
-
-		if ((res = ast_waitfor(s->chan, 25)) < 0) {
-			ast_debug(1, "Error waiting for a frame\n");
-			break;
-		}
-
-		last_frame = now;
-
-		/* Watchdog */
-		now = ast_tvnow();
-		if (ast_tvdiff_sec(now, start) > WATCHDOG_TOTAL_TIMEOUT || ast_tvdiff_sec(now, state_change) > WATCHDOG_STATE_TIMEOUT) {
-			ast_log(LOG_WARNING, "It looks like we hung. Aborting.\n");
-			res = -1;
-			break;
-		}
-
-		t38_terminal_send_timeout(&t38, ast_tvdiff_us(now, last_frame) / (1000000 / 8000));
-
-		if (!res) {
-			/* There was timeout waiting for a frame. Loop around and wait again */
-			continue;
-		}
-
-		/* There is a frame available. Get it */
-		res = 0;
-
-		if (!(inf = ast_read(s->chan))) {
-			ast_debug(1, "Channel hangup\n");
-			res = -1;
-			break;
-		}
-
-		ast_debug(10, "frame %d/%d, len=%d\n", inf->frametype, inf->subclass.integer, inf->datalen);
-
-		if (inf->frametype == AST_FRAME_MODEM && inf->subclass.integer == AST_MODEM_T38) {
-			t38_core_rx_ifp_packet(t38state, inf->data.ptr, inf->datalen, inf->seqno);
-			if (last_state != t30state->state) {
-				state_change = ast_tvnow();
-				last_state = t30state->state;
-			}
-		} else if (inf->frametype == AST_FRAME_CONTROL && inf->subclass.integer == AST_CONTROL_T38_PARAMETERS) {
-			struct ast_control_t38_parameters *parameters = inf->data.ptr;
-			if (parameters->request_response == AST_T38_TERMINATED) {
-				ast_debug(1, "T38 down, finishing\n");
-				break;
-			}
-		}
-
-		ast_frfree(inf);
-		inf = NULL;
-	}
-
-	ast_debug(1, "Loop finished, res=%d\n", res);
-
-	if (inf)
-		ast_frfree(inf);
-
-	t30_terminate(t30state);
-	t38_terminal_release(&t38);
-
-disable_t38:
-	/* if we are not the caller, it's our job to shut down the T.38
-	 * session when the FAX transmisson is complete.
-	 */
-	if ((s->caller_mode == FALSE) &&
-	    (ast_channel_get_t38_state(s->chan) == T38_STATE_NEGOTIATED)) {
-		struct ast_control_t38_parameters t38_parameters = { .request_response = AST_T38_REQUEST_TERMINATE, };
-
-		if (ast_indicate_data(s->chan, AST_CONTROL_T38_PARAMETERS, &t38_parameters, sizeof(t38_parameters)) == 0) {
-			/* wait up to five seconds for negotiation to complete */
-			unsigned int timeout = 5000;
-			int ms;
-
-			ast_debug(1, "Shutting down T.38 on %s\n", ast_channel_name(s->chan));
-			while (timeout > 0) {
-				ms = ast_waitfor(s->chan, 1000);
-				if (ms < 0) {
-					ast_log(LOG_WARNING, "something bad happened while channel '%s' was polling.\n", ast_channel_name(s->chan));
-					return -1;
-				}
-				if (!ms) {
-					/* nothing happened */
-					if (timeout > 0) {
-						timeout -= 1000;
-						continue;
-					} else {
-						ast_log(LOG_WARNING, "channel '%s' timed-out during the T.38 shutdown.\n", ast_channel_name(s->chan));
-						break;
-					}
-				}
-				if (!(inf = ast_read(s->chan))) {
-					return -1;
-				}
-				if ((inf->frametype == AST_FRAME_CONTROL) &&
-				    (inf->subclass.integer == AST_CONTROL_T38_PARAMETERS) &&
-				    (inf->datalen == sizeof(t38_parameters))) {
-					struct ast_control_t38_parameters *parameters = inf->data.ptr;
-
-					switch (parameters->request_response) {
-					case AST_T38_TERMINATED:
-						ast_debug(1, "Shut down T.38 on %s\n", ast_channel_name(s->chan));
-						break;
-					case AST_T38_REFUSED:
-						ast_log(LOG_WARNING, "channel '%s' refused to disable T.38\n", ast_channel_name(s->chan));
-						break;
-					default:
-						ast_log(LOG_ERROR, "channel '%s' failed to disable T.38\n", ast_channel_name(s->chan));
-						break;
-					}
-					ast_frfree(inf);
-					break;
-				}
-				ast_frfree(inf);
-			}
-		}
-	}
-
-	return res;
-}
-
-static int transmit(fax_session *s)
-{
-	int res = 0;
-
-	/* Clear all channel variables which to be set by the application.
-	   Pre-set status to error so in case of any problems we can just leave */
-	pbx_builtin_setvar_helper(s->chan, "FAXSTATUS", "FAILED");
-	pbx_builtin_setvar_helper(s->chan, "FAXERROR", "Channel problems");
-
-	pbx_builtin_setvar_helper(s->chan, "FAXMODE", NULL);
-	pbx_builtin_setvar_helper(s->chan, "REMOTESTATIONID", NULL);
-	pbx_builtin_setvar_helper(s->chan, "FAXPAGES", "0");
-	pbx_builtin_setvar_helper(s->chan, "FAXRESOLUTION", NULL);
-	pbx_builtin_setvar_helper(s->chan, "FAXBITRATE", NULL);
-
-	if (ast_channel_state(s->chan) != AST_STATE_UP) {
-		/* Shouldn't need this, but checking to see if channel is already answered
-		 * Theoretically asterisk should already have answered before running the app */
-		res = ast_answer(s->chan);
-		if (res) {
-			ast_log(LOG_WARNING, "Could not answer channel '%s'\n", ast_channel_name(s->chan));
-			return res;
-		}
-	}
-
-	s->t38state = ast_channel_get_t38_state(s->chan);
-	if (s->t38state != T38_STATE_NEGOTIATED) {
-		/* T38 is not negotiated on the channel yet. First start regular transmission. If it switches to T38, follow */
-		pbx_builtin_setvar_helper(s->chan, "FAXMODE", "audio");
-		res = transmit_audio(s);
-		if (res > 0) {
-			/* transmit_audio reports switchover to T38. Update t38state */
-			s->t38state = ast_channel_get_t38_state(s->chan);
-			if (s->t38state != T38_STATE_NEGOTIATED) {
-				ast_log(LOG_ERROR, "Audio loop reports T38 switchover but t38state != T38_STATE_NEGOTIATED\n");
-			}
-		}
-	}
-
-	if (s->t38state == T38_STATE_NEGOTIATED) {
-		pbx_builtin_setvar_helper(s->chan, "FAXMODE", "T38");
-		res = transmit_t38(s);
-	}
-
-	if (res) {
-		ast_log(LOG_WARNING, "Transmission error\n");
-		res = -1;
-	} else if (s->finished < 0) {
-		ast_log(LOG_WARNING, "Transmission failed\n");
-	} else if (s->finished > 0) {
-		ast_debug(1, "Transmission finished Ok\n");
-	}
-
-	return res;
-}
-
-/* === Application functions === */
-
-static int sndfax_exec(struct ast_channel *chan, const char *data)
-{
-	int res = 0;
-	char *parse;
-	fax_session session = { 0, };
-	char restore_digit_detect = 0;
-
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(file_name);
-		AST_APP_ARG(options);
-	);
-
-	if (chan == NULL) {
-		ast_log(LOG_ERROR, "Fax channel is NULL. Giving up.\n");
-		return -1;
-	}
-
-	/* The next few lines of code parse out the filename and header from the input string */
-	if (ast_strlen_zero(data)) {
-		/* No data implies no filename or anything is present */
-		ast_log(LOG_ERROR, "SendFAX requires an argument (filename)\n");
-		return -1;
-	}
-
-	parse = ast_strdupa(data);
-	AST_STANDARD_APP_ARGS(args, parse);
-
-	session.caller_mode = TRUE;
-
-	if (args.options) {
-		if (strchr(args.options, 'a'))
-			session.caller_mode = FALSE;
-	}
-
-	/* Done parsing */
-	session.direction = 1;
-	session.file_name = args.file_name;
-	session.chan = chan;
-	session.finished = 0;
-
-	/* get current digit detection mode, then disable digit detection if enabled */
-	{
-		int dummy = sizeof(restore_digit_detect);
-
-		ast_channel_queryoption(chan, AST_OPTION_DIGIT_DETECT, &restore_digit_detect, &dummy, 0);
-	}
-
-	if (restore_digit_detect) {
-		char new_digit_detect = 0;
-
-		ast_channel_setoption(chan, AST_OPTION_DIGIT_DETECT, &new_digit_detect, sizeof(new_digit_detect), 0);
-	}
-
-	/* disable FAX tone detection if enabled */
-	{
-		char new_fax_detect = 0;
-
-		ast_channel_setoption(chan, AST_OPTION_FAX_DETECT, &new_fax_detect, sizeof(new_fax_detect), 0);
-	}
-
-	res = transmit(&session);
-
-	if (restore_digit_detect) {
-		ast_channel_setoption(chan, AST_OPTION_DIGIT_DETECT, &restore_digit_detect, sizeof(restore_digit_detect), 0);
-	}
-
-	return res;
-}
-
-static int rcvfax_exec(struct ast_channel *chan, const char *data)
-{
-	int res = 0;
-	char *parse;
-	fax_session session;
-	char restore_digit_detect = 0;
-
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(file_name);
-		AST_APP_ARG(options);
-	);
-
-	if (chan == NULL) {
-		ast_log(LOG_ERROR, "Fax channel is NULL. Giving up.\n");
-		return -1;
-	}
-
-	/* The next few lines of code parse out the filename and header from the input string */
-	if (ast_strlen_zero(data)) {
-		/* No data implies no filename or anything is present */
-		ast_log(LOG_ERROR, "ReceiveFAX requires an argument (filename)\n");
-		return -1;
-	}
-
-	parse = ast_strdupa(data);
-	AST_STANDARD_APP_ARGS(args, parse);
-
-	session.caller_mode = FALSE;
-
-	if (args.options) {
-		if (strchr(args.options, 'c'))
-			session.caller_mode = TRUE;
-	}
-
-	/* Done parsing */
-	session.direction = 0;
-	session.file_name = args.file_name;
-	session.chan = chan;
-	session.finished = 0;
-
-	/* get current digit detection mode, then disable digit detection if enabled */
-	{
-		int dummy = sizeof(restore_digit_detect);
-
-		ast_channel_queryoption(chan, AST_OPTION_DIGIT_DETECT, &restore_digit_detect, &dummy, 0);
-	}
-
-	if (restore_digit_detect) {
-		char new_digit_detect = 0;
-
-		ast_channel_setoption(chan, AST_OPTION_DIGIT_DETECT, &new_digit_detect, sizeof(new_digit_detect), 0);
-	}
-
-	/* disable FAX tone detection if enabled */
-	{
-		char new_fax_detect = 0;
-
-		ast_channel_setoption(chan, AST_OPTION_FAX_DETECT, &new_fax_detect, sizeof(new_fax_detect), 0);
-	}
-
-	res = transmit(&session);
-
-	if (restore_digit_detect) {
-		ast_channel_setoption(chan, AST_OPTION_DIGIT_DETECT, &restore_digit_detect, sizeof(restore_digit_detect), 0);
-	}
-
-	return res;
-}
-
-static int unload_module(void)
-{
-	int res;
-
-	res = ast_unregister_application(app_sndfax_name);
-	res |= ast_unregister_application(app_rcvfax_name);
-
-	return res;
-}
-
-static int load_module(void)
-{
-	int res ;
-
-	res = ast_register_application_xml(app_sndfax_name, sndfax_exec);
-	res |= ast_register_application_xml(app_rcvfax_name, rcvfax_exec);
-
-	/* The default SPAN message handler prints to stderr. It is something we do not want */
-	span_set_message_handler(NULL);
-
-	return res;
-}
-
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Simple FAX Application",
-	.support_level = AST_MODULE_SUPPORT_DEPRECATED,
-	.load = load_module,
-	.unload = unload_module,
-);
diff --git a/apps/app_festival.c b/apps/app_festival.c
index c18725dbb6caa6888f1b2fee0cc945a21bb27638..5348bc79da25ee8dcfdd1f4a4aad4ca850902d97 100644
--- a/apps/app_festival.c
+++ b/apps/app_festival.c
@@ -433,7 +433,7 @@ static int festival_exec(struct ast_channel *chan, const char *vdata)
 	}
 	readcache = 0;
 	writecache = 0;
-	if (strlen(cachedir) + strlen(MD5Hex) + 1 <= MAXFESTLEN && (usecache == -1)) {
+	if (strlen(cachedir) + sizeof(MD5Hex) + 1 <= MAXFESTLEN && (usecache == -1)) {
 		snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
 		fdesc = open(cachefile, O_RDWR);
 		if (fdesc == -1) {
diff --git a/apps/app_ices.c b/apps/app_ices.c
deleted file mode 100644
index 38c68c0cae753f8a97cf0d7d07bd33439b4f40e6..0000000000000000000000000000000000000000
--- a/apps/app_ices.c
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2005, Digium, Inc.
- *
- * Mark Spencer <markster@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Stream to an icecast server via ICES (see contrib/asterisk-ices.xml)
- *
- * \author Mark Spencer <markster@digium.com>
- *
- * ICES - http://www.icecast.org/ices.php
- *
- * \ingroup applications
- */
-
-/*** MODULEINFO
-	<support_level>deprecated</support_level>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include "asterisk.h"
-
-#include <signal.h>
-#include <fcntl.h>
-#include <sys/time.h>
-
-#include "asterisk/paths.h"	/* use ast_config_AST_CONFIG_DIR */
-#include "asterisk/lock.h"
-#include "asterisk/file.h"
-#include "asterisk/channel.h"
-#include "asterisk/frame.h"
-#include "asterisk/pbx.h"
-#include "asterisk/module.h"
-#include "asterisk/translate.h"
-#include "asterisk/app.h"
-#include "asterisk/format_cache.h"
-
-/*** DOCUMENTATION
-	<application name="ICES" language="en_US">
-		<synopsis>
-			Encode and stream using 'ices'.
-		</synopsis>
-		<syntax>
-			<parameter name="config" required="true">
-				<para>ICES configuration file.</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>Streams to an icecast server using ices (available separately).
-			A configuration file must be supplied for ices (see contrib/asterisk-ices.xml).</para>
-			<note><para>ICES version 2 client and server required.</para></note>
-		</description>
-	</application>
-
- ***/
-
-#define path_BIN "/usr/bin/"
-#define path_LOCAL "/usr/local/bin/"
-
-static char *app = "ICES";
-
-static int icesencode(char *filename, int fd)
-{
-	int res;
-
-	res = ast_safe_fork(0);
-	if (res < 0)
-		ast_log(LOG_WARNING, "Fork failed\n");
-	if (res) {
-		return res;
-	}
-
-	if (ast_opt_high_priority)
-		ast_set_priority(0);
-	dup2(fd, STDIN_FILENO);
-	ast_close_fds_above_n(STDERR_FILENO);
-
-	/* Most commonly installed in /usr/local/bin
-	 * But many places has it in /usr/bin
-	 * As a last-ditch effort, try to use PATH
-	 */
-	execl(path_LOCAL "ices2", "ices", filename, SENTINEL);
-	execl(path_BIN "ices2", "ices", filename, SENTINEL);
-	execlp("ices2", "ices", filename, SENTINEL);
-
-	ast_debug(1, "Couldn't find ices version 2, attempting to use ices version 1.\n");
-
-	execl(path_LOCAL "ices", "ices", filename, SENTINEL);
-	execl(path_BIN "ices", "ices", filename, SENTINEL);
-	execlp("ices", "ices", filename, SENTINEL);
-
-	ast_log(LOG_WARNING, "Execute of ices failed, could not find command.\n");
-	close(fd);
-	_exit(0);
-}
-
-static int ices_exec(struct ast_channel *chan, const char *data)
-{
-	int res = 0;
-	int fds[2];
-	int ms = -1;
-	int pid = -1;
-	struct ast_format *oreadformat;
-	struct ast_frame *f;
-	char filename[256]="";
-	char *c;
-
-	if (ast_strlen_zero(data)) {
-		ast_log(LOG_WARNING, "ICES requires an argument (configfile.xml)\n");
-		return -1;
-	}
-
-	if (pipe(fds)) {
-		ast_log(LOG_WARNING, "Unable to create pipe\n");
-		return -1;
-	}
-	ast_fd_set_flags(fds[1], O_NONBLOCK);
-
-	ast_stopstream(chan);
-
-	if (ast_channel_state(chan) != AST_STATE_UP)
-		res = ast_answer(chan);
-
-	if (res) {
-		close(fds[0]);
-		close(fds[1]);
-		ast_log(LOG_WARNING, "Answer failed!\n");
-		return -1;
-	}
-
-	oreadformat = ao2_bump(ast_channel_readformat(chan));
-	res = ast_set_read_format(chan, ast_format_slin);
-	if (res < 0) {
-		close(fds[0]);
-		close(fds[1]);
-		ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
-		ao2_cleanup(oreadformat);
-		return -1;
-	}
-	if (((char *)data)[0] == '/')
-		ast_copy_string(filename, (char *) data, sizeof(filename));
-	else
-		snprintf(filename, sizeof(filename), "%s/%s", ast_config_AST_CONFIG_DIR, (char *)data);
-	/* Placeholder for options */
-	c = strchr(filename, '|');
-	if (c)
-		*c = '\0';
-	res = icesencode(filename, fds[0]);
-	if (res >= 0) {
-		pid = res;
-		for (;;) {
-			/* Wait for audio, and stream */
-			ms = ast_waitfor(chan, -1);
-			if (ms < 0) {
-				ast_debug(1, "Hangup detected\n");
-				res = -1;
-				break;
-			}
-			f = ast_read(chan);
-			if (!f) {
-				ast_debug(1, "Null frame == hangup() detected\n");
-				res = -1;
-				break;
-			}
-			if (f->frametype == AST_FRAME_VOICE) {
-				res = write(fds[1], f->data.ptr, f->datalen);
-				if (res < 0) {
-					if (errno != EAGAIN) {
-						ast_log(LOG_WARNING, "Write failed to pipe: %s\n", strerror(errno));
-						res = -1;
-						ast_frfree(f);
-						break;
-					}
-				}
-			}
-			ast_frfree(f);
-		}
-	}
-	close(fds[0]);
-	close(fds[1]);
-
-	if (pid > -1)
-		kill(pid, SIGKILL);
-	if (!res && oreadformat)
-		ast_set_read_format(chan, oreadformat);
-	ao2_cleanup(oreadformat);
-
-	return res;
-}
-
-static int unload_module(void)
-{
-	return ast_unregister_application(app);
-}
-
-static int load_module(void)
-{
-	return ast_register_application_xml(app, ices_exec);
-}
-
-AST_MODULE_INFO_STANDARD_DEPRECATED(ASTERISK_GPL_KEY, "Encode and Stream via icecast and ices");
diff --git a/apps/app_if.c b/apps/app_if.c
new file mode 100644
index 0000000000000000000000000000000000000000..bc04ffd71bb570c53cb3f1e744b8285f80d697b6
--- /dev/null
+++ b/apps/app_if.c
@@ -0,0 +1,383 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright 2022, Naveen Albert <asterisk@phreaknet.org>
+ *
+ * Naveen Albert <asterisk@phreaknet.org>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief If Branch Implementation
+ *
+ * \author Naveen Albert <asterisk@phreaknet.org>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+	<support_level>extended</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+
+/*** DOCUMENTATION
+	<application name="If" language="en_US">
+		<synopsis>
+			Start an if branch.
+		</synopsis>
+		<syntax>
+			<parameter name="expr" required="true" />
+		</syntax>
+		<description>
+			<para>Start an If branch.  Execution will continue inside the branch
+			if expr is true.</para>
+			<note><para>This application (and related applications) set variables
+			internally during execution.</para></note>
+		</description>
+		<see-also>
+			<ref type="application">ElseIf</ref>
+			<ref type="application">Else</ref>
+			<ref type="application">EndIf</ref>
+			<ref type="application">ExitIf</ref>
+		</see-also>
+	</application>
+	<application name="ElseIf" language="en_US">
+		<synopsis>
+			Start an else if branch.
+		</synopsis>
+		<syntax>
+			<parameter name="expr" required="true" />
+		</syntax>
+		<description>
+			<para>Start an optional ElseIf branch. Execution will continue inside the branch
+			if expr is true and if previous If and ElseIf branches evaluated to false.</para>
+			<para>Please note that execution inside a true If branch will fallthrough into
+			ElseIf unless the If segment is terminated with an ExitIf call. This is only
+			necessary with ElseIf but not with Else.</para>
+		</description>
+		<see-also>
+			<ref type="application">If</ref>
+			<ref type="application">Else</ref>
+			<ref type="application">EndIf</ref>
+			<ref type="application">ExitIf</ref>
+		</see-also>
+	</application>
+	<application name="Else" language="en_US">
+		<synopsis>
+			Define an optional else branch.
+		</synopsis>
+		<syntax>
+			<parameter name="expr" required="true" />
+		</syntax>
+		<description>
+			<para>Start an Else branch. Execution will jump here if all previous
+			If and ElseIf branches evaluated to false.</para>
+		</description>
+		<see-also>
+			<ref type="application">If</ref>
+			<ref type="application">ElseIf</ref>
+			<ref type="application">EndIf</ref>
+			<ref type="application">ExitIf</ref>
+		</see-also>
+	</application>
+	<application name="EndIf" language="en_US">
+		<synopsis>
+			End an if branch.
+		</synopsis>
+		<syntax />
+		<description>
+			<para>Ends the branch begun by the preceding <literal>If()</literal> application.</para>
+		</description>
+		<see-also>
+			<ref type="application">If</ref>
+			<ref type="application">ElseIf</ref>
+			<ref type="application">Else</ref>
+			<ref type="application">ExitIf</ref>
+		</see-also>
+	</application>
+	<application name="ExitIf" language="en_US">
+		<synopsis>
+			End an If branch.
+		</synopsis>
+		<syntax />
+		<description>
+			<para>Exits an <literal>If()</literal> branch, whether or not it has completed.</para>
+		</description>
+		<see-also>
+			<ref type="application">If</ref>
+			<ref type="application">ElseIf</ref>
+			<ref type="application">Else</ref>
+			<ref type="application">EndIf</ref>
+		</see-also>
+	</application>
+ ***/
+
+static char *if_app = "If";
+static char *elseif_app = "ElseIf";
+static char *else_app = "Else";
+static char *stop_app = "EndIf";
+static char *exit_app = "ExitIf";
+
+#define VAR_SIZE 64
+
+static const char *get_index(struct ast_channel *chan, const char *prefix, int idx)
+{
+	char varname[VAR_SIZE];
+
+	snprintf(varname, VAR_SIZE, "%s_%d", prefix, idx);
+	return pbx_builtin_getvar_helper(chan, varname);
+}
+
+static struct ast_exten *find_matching_priority(struct ast_context *c, const char *exten, int priority, const char *callerid)
+{
+	struct ast_exten *e;
+	struct ast_context *c2;
+	int idx;
+
+	for (e = ast_walk_context_extensions(c, NULL); e; e = ast_walk_context_extensions(c, e)) {
+		if (ast_extension_match(ast_get_extension_name(e), exten)) {
+			int needmatch = ast_get_extension_matchcid(e);
+			if ((needmatch && ast_extension_match(ast_get_extension_cidmatch(e), callerid)) ||
+				(!needmatch)) {
+				/* This is the matching extension we want */
+				struct ast_exten *p;
+				for (p = ast_walk_extension_priorities(e, NULL); p; p = ast_walk_extension_priorities(e, p)) {
+					if (priority != ast_get_extension_priority(p))
+						continue;
+					return p;
+				}
+			}
+		}
+	}
+
+	/* No match; run through includes */
+	for (idx = 0; idx < ast_context_includes_count(c); idx++) {
+		const struct ast_include *i = ast_context_includes_get(c, idx);
+
+		for (c2 = ast_walk_contexts(NULL); c2; c2 = ast_walk_contexts(c2)) {
+			if (!strcmp(ast_get_context_name(c2), ast_get_include_name(i))) {
+				e = find_matching_priority(c2, exten, priority, callerid);
+				if (e)
+					return e;
+			}
+		}
+	}
+	return NULL;
+}
+
+static int find_matching_endif(struct ast_channel *chan, const char *otherapp)
+{
+	struct ast_context *c;
+	int res = -1;
+
+	if (ast_rdlock_contexts()) {
+		ast_log(LOG_ERROR, "Failed to lock contexts list\n");
+		return -1;
+	}
+
+	for (c = ast_walk_contexts(NULL); c; c = ast_walk_contexts(c)) {
+		struct ast_exten *e;
+
+		if (!ast_rdlock_context(c)) {
+			if (!strcmp(ast_get_context_name(c), ast_channel_context(chan))) {
+				/* This is the matching context we want */
+				int cur_priority = ast_channel_priority(chan) + 1, level = 1;
+
+				for (e = find_matching_priority(c, ast_channel_exten(chan), cur_priority,
+					S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL));
+					e;
+					e = find_matching_priority(c, ast_channel_exten(chan), ++cur_priority,
+						S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
+					if (!strcasecmp(ast_get_extension_app(e), "IF")) {
+						level++;
+					} else if (!strcasecmp(ast_get_extension_app(e), "ENDIF")) {
+						level--;
+					}
+
+					if (!otherapp && level == 0) {
+						res = cur_priority;
+						break;
+					} else if (otherapp && level == 1 && !strcasecmp(ast_get_extension_app(e), otherapp)) {
+						res = cur_priority;
+						break;
+					}
+				}
+			}
+			ast_unlock_context(c);
+			if (res > 0) {
+				break;
+			}
+		}
+	}
+	ast_unlock_contexts();
+	return res;
+}
+
+static int if_helper(struct ast_channel *chan, const char *data, int end)
+{
+	int res = 0;
+	const char *if_pri = NULL;
+	char *my_name = NULL;
+	const char *label = NULL;
+	char varname[VAR_SIZE + 3]; /* + IF_ */
+	char end_varname[sizeof(varname) + 4]; /* + END_ + sizeof(varname) */
+	const char *prefix = "IF";
+	size_t size = 0;
+	int used_index_i = -1, x = 0;
+	char used_index[VAR_SIZE] = "0", new_index[VAR_SIZE] = "0";
+
+	if (!chan) {
+		return -1;
+	}
+
+	for (x = 0 ;; x++) {
+		if (get_index(chan, prefix, x)) {
+			used_index_i = x;
+		} else {
+			break;
+		}
+	}
+
+	snprintf(used_index, sizeof(used_index), "%d", used_index_i);
+	snprintf(new_index, sizeof(new_index), "%d", used_index_i + 1);
+
+	size = strlen(ast_channel_context(chan)) + strlen(ast_channel_exten(chan)) + 32;
+	my_name = ast_alloca(size);
+	memset(my_name, 0, size);
+	snprintf(my_name, size, "%s_%s_%d", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
+
+	ast_channel_lock(chan);
+	if (end > 1) {
+		label = used_index;
+	} else if (!(label = pbx_builtin_getvar_helper(chan, my_name))) {
+		label = new_index;
+		pbx_builtin_setvar_helper(chan, my_name, label);
+	}
+	snprintf(varname, sizeof(varname), "%s_%s", prefix, label);
+	if ((if_pri = pbx_builtin_getvar_helper(chan, varname)) && !end) {
+		if_pri = ast_strdupa(if_pri);
+		snprintf(end_varname,sizeof(end_varname),"END_%s",varname);
+	}
+	ast_channel_unlock(chan);
+
+	if ((end <= 1 && !pbx_checkcondition(ast_strdupa(data))) || (end > 1)) {
+		/* Condition Met (clean up helper vars) */
+		const char *goto_str;
+		int pri, endifpri;
+		pbx_builtin_setvar_helper(chan, varname, NULL);
+		pbx_builtin_setvar_helper(chan, my_name, NULL);
+		snprintf(end_varname,sizeof(end_varname),"END_%s",varname);
+		ast_channel_lock(chan);
+		endifpri = find_matching_endif(chan, NULL);
+		if ((goto_str = pbx_builtin_getvar_helper(chan, end_varname))) {
+			ast_parseable_goto(chan, goto_str);
+			pbx_builtin_setvar_helper(chan, end_varname, NULL);
+		} else if (end <= 1 && (pri = find_matching_endif(chan, "ElseIf")) > 0 && pri < endifpri) {
+			pri--; /* back up a priority, since it returned the priority after the ElseIf */
+			/* If is false, and ElseIf exists, so jump to ElseIf */
+			ast_verb(3, "Taking conditional false branch, jumping to priority %d\n", pri);
+			ast_channel_priority_set(chan, pri);
+		} else if (end <= 1 && (pri = find_matching_endif(chan, "Else")) > 0 && pri < endifpri) {
+			/* don't need to back up a priority, because we don't actually need to execute Else, just jump to the priority after. Directly executing Else will exit the conditional. */
+			/* If is false, and Else exists, so jump to Else */
+			ast_verb(3, "Taking absolute false branch, jumping to priority %d\n", pri);
+			ast_channel_priority_set(chan, pri);
+		} else {
+			pri = endifpri;
+			if (pri > 0) {
+				ast_verb(3, "Exiting conditional, jumping to priority %d\n", pri);
+				ast_channel_priority_set(chan, pri);
+			} else if (end == 4) { /* Condition added because of end > 0 instead of end == 4 */
+				ast_log(LOG_WARNING, "Couldn't find matching EndIf? (If at %s@%s priority %d)\n", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
+			}
+		}
+		ast_channel_unlock(chan);
+		return res;
+	}
+
+	if (end <= 1 && !if_pri) {
+		char *goto_str;
+		size = strlen(ast_channel_context(chan)) + strlen(ast_channel_exten(chan)) + 32;
+		goto_str = ast_alloca(size);
+		memset(goto_str, 0, size);
+		snprintf(goto_str, size, "%s,%s,%d", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan));
+		pbx_builtin_setvar_helper(chan, varname, goto_str);
+	} else if (end > 1 && if_pri) {
+		/* END of branch */
+		snprintf(end_varname, sizeof(end_varname), "END_%s", varname);
+		if (!pbx_builtin_getvar_helper(chan, end_varname)) {
+			char *goto_str;
+			size = strlen(ast_channel_context(chan)) + strlen(ast_channel_exten(chan)) + 32;
+			goto_str = ast_alloca(size);
+			memset(goto_str, 0, size);
+			snprintf(goto_str, size, "%s,%s,%d", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan)+1);
+			pbx_builtin_setvar_helper(chan, end_varname, goto_str);
+		}
+		ast_parseable_goto(chan, if_pri);
+	}
+
+	return res;
+}
+
+static int if_exec(struct ast_channel *chan, const char *data) {
+	return if_helper(chan, data, 0);
+}
+
+static int elseif_exec(struct ast_channel *chan, const char *data) {
+	return if_helper(chan, data, 1);
+}
+
+static int end_exec(struct ast_channel *chan, const char *data) {
+	return if_helper(chan, data, 2);
+}
+
+static int else_exec(struct ast_channel *chan, const char *data) {
+	return if_helper(chan, data, 3);
+}
+
+static int exit_exec(struct ast_channel *chan, const char *data) {
+	return if_helper(chan, data, 4);
+}
+
+static int unload_module(void)
+{
+	int res;
+
+	res = ast_unregister_application(if_app);
+	res |= ast_unregister_application(elseif_app);
+	res |= ast_unregister_application(stop_app);
+	res |= ast_unregister_application(else_app);
+	res |= ast_unregister_application(exit_app);
+
+	return res;
+}
+
+static int load_module(void)
+{
+	int res;
+
+	res = ast_register_application_xml(if_app, if_exec);
+	res |= ast_register_application_xml(elseif_app, elseif_exec);
+	res |= ast_register_application_xml(stop_app, end_exec);
+	res |= ast_register_application_xml(else_app, else_exec);
+	res |= ast_register_application_xml(exit_app, exit_exec);
+
+	return res;
+}
+
+AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "If Branch and Conditional Execution");
diff --git a/apps/app_image.c b/apps/app_image.c
deleted file mode 100644
index 53754eda64dbb1003782dd53300ee2a1c6160469..0000000000000000000000000000000000000000
--- a/apps/app_image.c
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2005, Digium, Inc.
- *
- * Mark Spencer <markster@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief App to transmit an image
- *
- * \author Mark Spencer <markster@digium.com>
- *
- * \ingroup applications
- */
-
-/*** MODULEINFO
-	<support_level>deprecated</support_level>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include "asterisk.h"
-
-#include "asterisk/pbx.h"
-#include "asterisk/module.h"
-#include "asterisk/image.h"
-
-static char *app = "SendImage";
-
-/*** DOCUMENTATION
-	<application name="SendImage" language="en_US">
-		<synopsis>
-			Sends an image file.
-		</synopsis>
-		<syntax>
-			<parameter name="filename" required="true">
-				<para>Path of the filename (image) to send.</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>Send an image file on a channel supporting it.</para>
-			<para>Result of transmission will be stored in <variable>SENDIMAGESTATUS</variable></para>
-			<variablelist>
-				<variable name="SENDIMAGESTATUS">
-					<value name="SUCCESS">
-						Transmission succeeded.
-					</value>
-					<value name="FAILURE">
-						Transmission failed.
-					</value>
-					<value name="UNSUPPORTED">
-						Image transmission not supported by channel.
-					</value>
-				</variable>
-			</variablelist>
-		</description>
-		<see-also>
-			<ref type="application">SendText</ref>
-			<ref type="application">SendURL</ref>
-		</see-also>
-	</application>
- ***/
-
-static int sendimage_exec(struct ast_channel *chan, const char *data)
-{
-
-	if (ast_strlen_zero(data)) {
-		ast_log(LOG_WARNING, "SendImage requires an argument (filename)\n");
-		return -1;
-	}
-
-	if (!ast_supports_images(chan)) {
-		/* Does not support transport */
-		pbx_builtin_setvar_helper(chan, "SENDIMAGESTATUS", "UNSUPPORTED");
-		return 0;
-	}
-
-	if (!ast_send_image(chan, data)) {
-		pbx_builtin_setvar_helper(chan, "SENDIMAGESTATUS", "SUCCESS");
-	} else {
-		pbx_builtin_setvar_helper(chan, "SENDIMAGESTATUS", "FAILURE");
-	}
-
-	return 0;
-}
-
-static int unload_module(void)
-{
-	return ast_unregister_application(app);
-}
-
-static int load_module(void)
-{
-	return ast_register_application_xml(app, sendimage_exec);
-}
-
-AST_MODULE_INFO_STANDARD_DEPRECATED(ASTERISK_GPL_KEY, "Image Transmission Application");
diff --git a/apps/app_meetme.c b/apps/app_meetme.c
index 99a824d1b062173956e9bb6c0953061c294ce1d7..8b5bd40d7cc96c7261c890339a123b8c8da92869 100644
--- a/apps/app_meetme.c
+++ b/apps/app_meetme.c
@@ -41,7 +41,7 @@
 /*** MODULEINFO
 	<depend>dahdi</depend>
 	<defaultenabled>no</defaultenabled>
-	<support_level>extended</support_level>
+	<support_level>deprecated</support_level>
 	<replacement>app_confbridge</replacement>
 	<deprecated_in>19</deprecated_in>
 	<removed_in>21</removed_in>
@@ -639,6 +639,82 @@
 			</syntax>
 		</managerEventInstance>
 	</managerEvent>
+	<managerEvent language="en_US" name="MeetmeList">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised in response to a MeetmeList command.</synopsis>
+			<syntax>
+				<parameter name="Conference">
+					<para>Conference ID.</para>
+				</parameter>
+				<parameter name="UserNumber">
+					<para>User ID.</para>
+				</parameter>
+				<parameter name="CallerIDNum">
+					<para>Caller ID number.</para>
+				</parameter>
+				<parameter name="CallerIDName">
+					<para>Caller ID name.</para>
+				</parameter>
+				<parameter name="ConnectedLineNum">
+					<para>Connected Line number.</para>
+				</parameter>
+				<parameter name="ConnectedLineName">
+					<para>Connected Line name.</para>
+				</parameter>
+				<parameter name="Channel">
+					<para>Channel name</para>
+				</parameter>
+				<parameter name="Admin">
+					<para>Whether or not the user is an admin.</para>
+				</parameter>
+				<parameter name="Role">
+					<para>User role. Can be "Listen only", "Talk only", or "Talk and listen".</para>
+				</parameter>
+				<parameter name="MarkedUser">
+					<para>Whether or not the user is a marked user.</para>
+				</parameter>
+				<parameter name="Muted">
+					<para>Whether or not the user is currently muted.</para>
+				</parameter>
+				<parameter name="Talking">
+					<para>Whether or not the user is currently talking.</para>
+				</parameter>
+			</syntax>
+			<see-also>
+				<ref type="manager">MeetmeList</ref>
+				<ref type="application">MeetMe</ref>
+			</see-also>
+		</managerEventInstance>
+	</managerEvent>
+	<managerEvent language="en_US" name="MeetmeListRooms">
+		<managerEventInstance class="EVENT_FLAG_CALL">
+			<synopsis>Raised in response to a MeetmeListRooms command.</synopsis>
+			<syntax>
+				<parameter name="Conference">
+					<para>Conference ID.</para>
+				</parameter>
+				<parameter name="Parties">
+					<para>Number of parties in the conference.</para>
+				</parameter>
+				<parameter name="Marked">
+					<para>Number of marked users in the conference.</para>
+				</parameter>
+				<parameter name="Activity">
+					<para>Total duration of conference in HH:MM:SS format.</para>
+				</parameter>
+				<parameter name="Creation">
+					<para>How the conference was created: "Dyanmic" or "Static".</para>
+				</parameter>
+				<parameter name="Locked">
+					<para>Whether or not the conference is locked.</para>
+				</parameter>
+			</syntax>
+			<see-also>
+				<ref type="manager">MeetmeListRooms</ref>
+				<ref type="application">MeetMe</ref>
+			</see-also>
+		</managerEventInstance>
+	</managerEvent>
  ***/
 
 #define CONFIG_FILE_NAME	"meetme.conf"
@@ -4761,7 +4837,7 @@ static struct ast_conference *find_conf(struct ast_channel *chan, char *confno,
 				}
 			}
 			if (!var) {
-				ast_debug(1, "%s isn't a valid conference\n", confno);
+				ast_log(LOG_WARNING, "%s isn't a valid conference\n", confno);
 			}
 			ast_config_destroy(cfg);
 		}
@@ -5239,7 +5315,9 @@ static int admin_exec(struct ast_channel *chan, const char *data) {
 
 	if (ast_strlen_zero(data)) {
 		ast_log(LOG_WARNING, "MeetMeAdmin requires an argument!\n");
-		pbx_builtin_setvar_helper(chan, "MEETMEADMINSTATUS", "NOPARSE");
+		if (chan) {
+			pbx_builtin_setvar_helper(chan, "MEETMEADMINSTATUS", "NOPARSE");
+		}
 		return -1;
 	}
 
@@ -5248,7 +5326,9 @@ static int admin_exec(struct ast_channel *chan, const char *data) {
 
 	if (!args.command) {
 		ast_log(LOG_WARNING, "MeetmeAdmin requires a command!\n");
-		pbx_builtin_setvar_helper(chan, "MEETMEADMINSTATUS", "NOPARSE");
+		if (chan) {
+			pbx_builtin_setvar_helper(chan, "MEETMEADMINSTATUS", "NOPARSE");
+		}
 		return -1;
 	}
 
@@ -5261,7 +5341,9 @@ static int admin_exec(struct ast_channel *chan, const char *data) {
 	if (!cnf) {
 		ast_log(LOG_WARNING, "Conference number '%s' not found!\n", args.confno);
 		AST_LIST_UNLOCK(&confs);
-		pbx_builtin_setvar_helper(chan, "MEETMEADMINSTATUS", "NOTFOUND");
+		if (chan) {
+			pbx_builtin_setvar_helper(chan, "MEETMEADMINSTATUS", "NOTFOUND");
+		}
 		return 0;
 	}
 
@@ -5384,7 +5466,9 @@ usernotfound:
 	AST_LIST_UNLOCK(&confs);
 
 	dispose_conf(cnf);
-	pbx_builtin_setvar_helper(chan, "MEETMEADMINSTATUS", res == -2 ? "NOTFOUND" : res ? "FAILED" : "OK");
+	if (chan) {
+		pbx_builtin_setvar_helper(chan, "MEETMEADMINSTATUS", res == -2 ? "NOTFOUND" : res ? "FAILED" : "OK");
+	}
 
 	return 0;
 }
@@ -6065,7 +6149,7 @@ struct run_station_args {
 
 static void answer_trunk_chan(struct ast_channel *chan)
 {
-	ast_answer(chan);
+	ast_raw_answer(chan);
 	ast_indicate(chan, -1);
 }
 
@@ -6910,8 +6994,18 @@ static void *dial_trunk(void *data)
 		return NULL;
 	}
 
-	for (;;) {
+	/* Wait for dial to end, while servicing the channel */
+	while (ast_waitfor(trunk_ref->chan, 100)) {
 		unsigned int done = 0;
+		struct ast_frame *fr = ast_read(trunk_ref->chan);
+
+		if (!fr) {
+			ast_debug(1, "Channel %s did not return a frame, must have hung up\n", ast_channel_name(trunk_ref->chan));
+			done = 1;
+			break;
+		}
+		ast_frfree(fr); /* Ignore while dialing */
+
 		switch ((dial_res = ast_dial_state(dial))) {
 		case AST_DIAL_RESULT_ANSWERED:
 			trunk_ref->trunk->chan = ast_dial_answered(dial);
@@ -6948,8 +7042,6 @@ static void *dial_trunk(void *data)
 			last_state = current_state;
 		}
 
-		/* avoid tight loop... sleep for 1/10th second */
-		ast_safe_sleep(trunk_ref->chan, 100);
 	}
 
 	if (!trunk_ref->trunk->chan) {
@@ -7108,8 +7200,10 @@ static int sla_station_exec(struct ast_channel *chan, const char *data)
 		sla_change_trunk_state(trunk_ref->trunk, SLA_TRUNK_STATE_UP, ALL_TRUNK_REFS, NULL);
 		/* Create a thread to dial the trunk and dump it into the conference.
 		 * However, we want to wait until the trunk has been dialed and the
-		 * conference is created before continuing on here. */
-		ast_autoservice_start(chan);
+		 * conference is created before continuing on here.
+		 * Don't autoservice the channel or we'll have multiple threads
+		 * handling it. dial_trunk services the channel.
+		 */
 		ast_mutex_init(&cond_lock);
 		ast_cond_init(&cond, NULL);
 		ast_mutex_lock(&cond_lock);
@@ -7118,7 +7212,7 @@ static int sla_station_exec(struct ast_channel *chan, const char *data)
 		ast_mutex_unlock(&cond_lock);
 		ast_mutex_destroy(&cond_lock);
 		ast_cond_destroy(&cond);
-		ast_autoservice_stop(chan);
+
 		if (!trunk_ref->trunk->chan) {
 			ast_debug(1, "Trunk didn't get created. chan: %lx\n", (unsigned long) trunk_ref->trunk->chan);
 			pbx_builtin_setvar_helper(chan, "SLASTATION_STATUS", "CONGESTION");
@@ -8080,7 +8174,7 @@ static int reload(void)
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "MeetMe conference bridge",
-	.support_level = AST_MODULE_SUPPORT_EXTENDED,
+	.support_level = AST_MODULE_SUPPORT_DEPRECATED,
 	.load = load_module,
 	.unload = unload_module,
 	.reload = reload,
diff --git a/apps/app_mf.c b/apps/app_mf.c
index 33c1aaa958a7704e49082f2af968a2a83e20bca3..5840b56924ddf4f67d692201824c2d937bf2f4ff 100644
--- a/apps/app_mf.c
+++ b/apps/app_mf.c
@@ -42,6 +42,11 @@
 
 /*** DOCUMENTATION
 	<application name="ReceiveMF" language="en_US">
+		<since>
+			<version>16.21.0</version>
+			<version>18.7.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Detects MF digits on a channel and saves them to a variable.
 		</synopsis>
@@ -113,6 +118,11 @@
 		</see-also>
 	</application>
 	<application name="SendMF" language="en_US">
+		<since>
+			<version>16.21.0</version>
+			<version>18.7.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Sends arbitrary MF digits on the current or specified channel.
 		</synopsis>
@@ -151,6 +161,11 @@
 		</see-also>
 	</application>
 	<manager name="PlayMF" language="en_US">
+		<since>
+			<version>16.21.0</version>
+			<version>18.7.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Play MF digit on a specific channel.
 		</synopsis>
@@ -220,13 +235,14 @@ static const char sendmf_name[] = "SendMF";
  * \param buflen Size of buffer
  * \param timeout ms to wait for all digits before giving up
  * \param features Any additional DSP features to use
+ * \param laxkp Receive digits even if KP not received
  * \param override Start over if we receive additional KPs
  * \param no_kp Don't include KP in the output
  * \param no_st Don't include start digits in the output
  * \param maxdigits If greater than 0, only read this many digits no matter what
  *
  * \retval 0 if successful
- * \retval -1 if unsuccessful.
+ * \retval -1 if unsuccessful (including hangup).
  */
 static int read_mf_digits(struct ast_channel *chan, char *buf, int buflen, int timeout, int features, int laxkp, int override, int no_kp, int no_st, int maxdigits) {
 	struct ast_dsp *dsp;
@@ -236,6 +252,7 @@ static int read_mf_digits(struct ast_channel *chan, char *buf, int buflen, int t
 	int digits_read = 0;
 	int is_start_digit = 0;
 	char *str = buf;
+	int res = 0;
 
 	if (!(dsp = ast_dsp_new())) {
 		ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
@@ -318,11 +335,12 @@ static int read_mf_digits(struct ast_channel *chan, char *buf, int buflen, int t
 			}
 		} else {
 			pbx_builtin_setvar_helper(chan, "RECEIVEMFSTATUS", "HANGUP");
+			res = -1;
 		}
 	}
 	ast_dsp_free(dsp);
 	ast_debug(3, "channel '%s' - event loop stopped { timeout: %d, remaining_time: %d }\n", ast_channel_name(chan), timeout, remaining_time);
-	return 0;
+	return res;
 }
 
 static int read_mf_exec(struct ast_channel *chan, const char *data)
@@ -334,7 +352,7 @@ static int read_mf_exec(struct ast_channel *chan, const char *data)
 	struct ast_flags flags = {0};
 	char *optargs[OPT_ARG_ARRAY_SIZE];
 	char *argcopy = NULL;
-	int features = 0, maxdigits = 0;
+	int res, features = 0, maxdigits = 0;
 
 	AST_DECLARE_APP_ARGS(arglist,
 		AST_APP_ARG(variable);
@@ -392,15 +410,15 @@ static int read_mf_exec(struct ast_channel *chan, const char *data)
 		features |= DSP_DIGITMODE_RELAXDTMF;
 	}
 
-	read_mf_digits(chan, tmp, BUFFER_SIZE, to, features, (ast_test_flag(&flags, OPT_LAX_KP)),
+	res = read_mf_digits(chan, tmp, BUFFER_SIZE, to, features, (ast_test_flag(&flags, OPT_LAX_KP)),
 		(ast_test_flag(&flags, OPT_KP_OVERRIDE)), (ast_test_flag(&flags, OPT_NO_KP)), (ast_test_flag(&flags, OPT_NO_ST)), maxdigits);
 	pbx_builtin_setvar_helper(chan, arglist.variable, tmp);
 	if (!ast_strlen_zero(tmp)) {
 		ast_verb(3, "MF digits received: '%s'\n", tmp);
-	} else {
+	} else if (!res) { /* if channel hung up, don't print anything out */
 		ast_verb(3, "No MF digits received.\n");
 	}
-	return 0;
+	return res;
 }
 
 static int sendmf_exec(struct ast_channel *chan, const char *vdata)
diff --git a/apps/app_mixmonitor.c b/apps/app_mixmonitor.c
index a0eb1dbf5deaf99e1783b74b33a2f01dbec7f2fc..361dcdad873dff22a0f767cfc54bfd9a5003a9f6 100644
--- a/apps/app_mixmonitor.c
+++ b/apps/app_mixmonitor.c
@@ -90,6 +90,16 @@
 						<para>Play a periodic beep while this call is being recorded.</para>
 						<argument name="interval"><para>Interval, in seconds. Default is 15.</para></argument>
 					</option>
+					<option name="c">
+						<para>Use the real Caller ID from the channel for the voicemail Caller ID.</para>
+						<para>By default, the Connected Line is used. If you want the channel caller's
+						real number, you may need to specify this option.</para>
+					</option>
+					<option name="d">
+						<para>Delete the recording file as soon as MixMonitor is done with it.</para>
+						<para>For example, if you use the m option to dispatch the recording to a voicemail box,
+						you can specify this option to delete the original copy of it afterwards.</para>
+					</option>
 					<option name="v">
 						<para>Adjust the <emphasis>heard</emphasis> volume by a factor of <replaceable>x</replaceable>
 						(range <literal>-4</literal> to <literal>4</literal>)</para>
@@ -407,6 +417,8 @@ enum mixmonitor_flags {
 	MUXFLAG_BEEP_STOP = (1 << 13),
 	MUXFLAG_DEPRECATED_RWSYNC = (1 << 14),
 	MUXFLAG_NO_RWSYNC = (1 << 15),
+	MUXFLAG_AUTO_DELETE = (1 << 16),
+	MUXFLAG_REAL_CALLERID = (1 << 17),
 };
 
 enum mixmonitor_args {
@@ -427,6 +439,8 @@ AST_APP_OPTIONS(mixmonitor_opts, {
 	AST_APP_OPTION('a', MUXFLAG_APPEND),
 	AST_APP_OPTION('b', MUXFLAG_BRIDGED),
 	AST_APP_OPTION_ARG('B', MUXFLAG_BEEP, OPT_ARG_BEEP_INTERVAL),
+	AST_APP_OPTION('c', MUXFLAG_REAL_CALLERID),
+	AST_APP_OPTION('d', MUXFLAG_AUTO_DELETE),
 	AST_APP_OPTION('p', MUXFLAG_BEEP_START),
 	AST_APP_OPTION('P', MUXFLAG_BEEP_STOP),
 	AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
@@ -860,6 +874,19 @@ static void *mixmonitor_thread(void *obj)
 		ast_debug(3, "No recipients to forward monitor to, moving on.\n");
 	}
 
+	if (ast_test_flag(mixmonitor, MUXFLAG_AUTO_DELETE)) {
+		ast_debug(3, "Deleting our copies of recording files\n");
+		if (!ast_strlen_zero(fs_ext)) {
+			ast_filedelete(mixmonitor->filename, fs_ext);
+		}
+		if (!ast_strlen_zero(fs_read_ext)) {
+			ast_filedelete(mixmonitor->filename_read, fs_ext);
+		}
+		if (!ast_strlen_zero(fs_write_ext)) {
+			ast_filedelete(mixmonitor->filename_write, fs_ext);
+		}
+	}
+
 	mixmonitor_free(mixmonitor);
 
 	ast_module_unref(ast_module_info->self);
@@ -1015,20 +1042,37 @@ static int launch_monitor_thread(struct ast_channel *chan, const char *filename,
 
 	if (!ast_strlen_zero(recipients)) {
 		char callerid[256];
-		struct ast_party_connected_line *connected;
 
 		ast_channel_lock(chan);
 
-		/* We use the connected line of the invoking channel for caller ID. */
-
-		connected = ast_channel_connected(chan);
-		ast_debug(3, "Connected Line CID = %d - %s : %d - %s\n", connected->id.name.valid,
-			connected->id.name.str, connected->id.number.valid,
-			connected->id.number.str);
-		ast_callerid_merge(callerid, sizeof(callerid),
-			S_COR(connected->id.name.valid, connected->id.name.str, NULL),
-			S_COR(connected->id.number.valid, connected->id.number.str, NULL),
-			"Unknown");
+		/* We use the connected line of the invoking channel for caller ID,
+		 * unless we've been told to use the Caller ID.
+		 * The initial use for this relied on Connected Line to get the
+		 * actual number for recording with Digium phones,
+		 * but in generic use the Caller ID is likely what people want.
+		 */
+
+		if (ast_test_flag(mixmonitor, MUXFLAG_REAL_CALLERID)) {
+			struct ast_party_caller *caller;
+			caller = ast_channel_caller(chan);
+			ast_debug(3, "Caller ID = %d - %s : %d - %s\n", caller->id.name.valid,
+				caller->id.name.str, caller->id.number.valid,
+				caller->id.number.str);
+			ast_callerid_merge(callerid, sizeof(callerid),
+				S_COR(caller->id.name.valid, caller->id.name.str, NULL),
+				S_COR(caller->id.number.valid, caller->id.number.str, NULL),
+				"Unknown");
+		} else {
+			struct ast_party_connected_line *connected;
+			connected = ast_channel_connected(chan);
+			ast_debug(3, "Connected Line CID = %d - %s : %d - %s\n", connected->id.name.valid,
+				connected->id.name.str, connected->id.number.valid,
+				connected->id.number.str);
+			ast_callerid_merge(callerid, sizeof(callerid),
+				S_COR(connected->id.name.valid, connected->id.name.str, NULL),
+				S_COR(connected->id.number.valid, connected->id.number.str, NULL),
+				"Unknown");
+		}
 
 		ast_string_field_set(mixmonitor, call_context, ast_channel_context(chan));
 		ast_string_field_set(mixmonitor, call_macrocontext, ast_channel_macrocontext(chan));
@@ -1403,6 +1447,50 @@ static char *handle_cli_mixmonitor(struct ast_cli_entry *e, int cmd, struct ast_
 	return CLI_SUCCESS;
 }
 
+/*! \brief  Mute / unmute  an individual MixMonitor by id */
+static int mute_mixmonitor_instance(struct ast_channel *chan, const char *data,
+									enum ast_audiohook_flags flag, int clearmute)
+{
+	struct ast_datastore *datastore = NULL;
+	char *parse = "";
+	struct mixmonitor_ds *mixmonitor_ds;
+
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(mixmonid);
+	);
+
+	if (!ast_strlen_zero(data)) {
+		parse = ast_strdupa(data);
+	}
+
+	AST_STANDARD_APP_ARGS(args, parse);
+
+	ast_channel_lock(chan);
+
+	datastore = ast_channel_datastore_find(chan, &mixmonitor_ds_info,
+		S_OR(args.mixmonid, NULL));
+	if (!datastore) {
+		ast_channel_unlock(chan);
+		return -1;
+	}
+	mixmonitor_ds = datastore->data;
+
+	ast_mutex_lock(&mixmonitor_ds->lock);
+
+	if (mixmonitor_ds->audiohook) {
+		if (clearmute) {
+			ast_clear_flag(mixmonitor_ds->audiohook, flag);
+		} else {
+			ast_set_flag(mixmonitor_ds->audiohook, flag);
+		}
+	}
+
+	ast_mutex_unlock(&mixmonitor_ds->lock);
+	ast_channel_unlock(chan);
+
+	return 0;
+}
+
 /*! \brief  Mute / unmute  a MixMonitor channel */
 static int manager_mute_mixmonitor(struct mansession *s, const struct message *m)
 {
@@ -1411,7 +1499,8 @@ static int manager_mute_mixmonitor(struct mansession *s, const struct message *m
 	const char *id = astman_get_header(m, "ActionID");
 	const char *state = astman_get_header(m, "State");
 	const char *direction = astman_get_header(m,"Direction");
-	int clearmute = 1;
+	const char *mixmonitor_id = astman_get_header(m, "MixMonitorID");
+	int clearmute = 1, mutedcount = 0;
 	enum ast_audiohook_flags flag;
 	RAII_VAR(struct stasis_message *, stasis_message, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_json *, stasis_message_blob, NULL, ast_json_unref);
@@ -1450,15 +1539,28 @@ static int manager_mute_mixmonitor(struct mansession *s, const struct message *m
 		return AMI_SUCCESS;
 	}
 
-	if (ast_audiohook_set_mute(c, mixmonitor_spy_type, flag, clearmute)) {
-		ast_channel_unref(c);
-		astman_send_error(s, m, "Cannot set mute flag");
-		return AMI_SUCCESS;
+	if (ast_strlen_zero(mixmonitor_id)) {
+		mutedcount = ast_audiohook_set_mute_all(c, mixmonitor_spy_type, flag, clearmute);
+		if (mutedcount < 0) {
+			ast_channel_unref(c);
+			astman_send_error(s, m, "Cannot set mute flag");
+			return AMI_SUCCESS;
+		}
+	} else {
+		if (mute_mixmonitor_instance(c, mixmonitor_id, flag, clearmute)) {
+			ast_channel_unref(c);
+			astman_send_error(s, m, "Cannot set mute flag");
+			return AMI_SUCCESS;
+		}
+		mutedcount = 1;
 	}
 
-	stasis_message_blob = ast_json_pack("{s: s, s: b}",
+
+	stasis_message_blob = ast_json_pack("{s: s, s: b, s: s, s: i}",
 		"direction", direction,
-		"state", ast_true(state));
+		"state", ast_true(state),
+		"mixmonitorid", mixmonitor_id,
+		"count", mutedcount);
 
 	stasis_message = ast_channel_blob_create_from_cache(ast_channel_uniqueid(c),
 		ast_channel_mixmonitor_mute_type(), stasis_message_blob);
diff --git a/apps/app_mp3.c b/apps/app_mp3.c
index 9bc23c0477a1f95fb29f813d0d49eb88f1420947..ef342b30cc38a0d12221b9b13e5590d402687eb9 100644
--- a/apps/app_mp3.c
+++ b/apps/app_mp3.c
@@ -101,7 +101,7 @@ static int mp3play(const char *filename, unsigned int sampling_rate, int fd)
 	/* Execute mpg123, but buffer if it's a net connection */
 	if (!strncasecmp(filename, "http://", 7) && strstr(filename, ".m3u")) {
 	    char buffer_size_str[8];
-	    snprintf(buffer_size_str, 8, "%u", (int) 0.5*2*sampling_rate/1000); // 0.5 seconds for a live stream
+	    snprintf(buffer_size_str, 8, "%u", (int) 0.5*2*sampling_rate/1000); /* 0.5 seconds for a live stream */
 		/* Most commonly installed in /usr/local/bin */
 	    execl(LOCAL_MPG_123, "mpg123", "-e", "s16", "-q", "-s", "-b", buffer_size_str, "-f", "8192", "--mono", "-r", sampling_rate_str, "-@", filename, (char *)NULL);
 		/* But many places has it in /usr/bin */
@@ -111,7 +111,7 @@ static int mp3play(const char *filename, unsigned int sampling_rate, int fd)
 	}
 	else if (!strncasecmp(filename, "http://", 7)) {
 	    char buffer_size_str[8];
-	    snprintf(buffer_size_str, 8, "%u", 6*2*sampling_rate/1000); // 6 seconds for a remote MP3 file
+	    snprintf(buffer_size_str, 8, "%u", 6*2*sampling_rate/1000); /* 6 seconds for a remote MP3 file */
 		/* Most commonly installed in /usr/local/bin */
 	    execl(LOCAL_MPG_123, "mpg123", "-e", "s16", "-q", "-s", "-b", buffer_size_str, "-f", "8192", "--mono", "-r", sampling_rate_str, filename, (char *)NULL);
 		/* But many places has it in /usr/bin */
diff --git a/apps/app_nbscat.c b/apps/app_nbscat.c
deleted file mode 100644
index c714cbb64b8d35cbfc6df9c77ce97631db0def05..0000000000000000000000000000000000000000
--- a/apps/app_nbscat.c
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2005, Digium, Inc.
- *
- * Mark Spencer <markster@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Silly application to play an NBScat file -- uses nbscat8k
- *
- * \author Mark Spencer <markster@digium.com>
- *
- * \ingroup applications
- */
-
-/*** MODULEINFO
-	<support_level>deprecated</support_level>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include "asterisk.h"
-
-#include <fcntl.h>
-#include <sys/time.h>
-#include <sys/socket.h>
-#include <signal.h>
-
-#include "asterisk/lock.h"
-#include "asterisk/file.h"
-#include "asterisk/channel.h"
-#include "asterisk/frame.h"
-#include "asterisk/pbx.h"
-#include "asterisk/module.h"
-#include "asterisk/translate.h"
-#include "asterisk/app.h"
-#include "asterisk/format_cache.h"
-
-/*** DOCUMENTATION
-	<application name="NBScat" language="en_US">
-		<synopsis>
-			Play an NBS local stream.
-		</synopsis>
-		<syntax />
-		<description>
-			<para>Executes nbscat to listen to the local NBS stream.
-			User can exit by pressing any key.</para>
-		</description>
-	</application>
- ***/
-
-#define LOCAL_NBSCAT "/usr/local/bin/nbscat8k"
-#define NBSCAT "/usr/bin/nbscat8k"
-
-#ifndef AF_LOCAL
-#define AF_LOCAL AF_UNIX
-#endif
-
-static char *app = "NBScat";
-
-static int NBScatplay(int fd)
-{
-	int res;
-
-	res = ast_safe_fork(0);
-	if (res < 0) {
-		ast_log(LOG_WARNING, "Fork failed\n");
-	}
-
-	if (res) {
-		return res;
-	}
-
-	if (ast_opt_high_priority)
-		ast_set_priority(0);
-
-	dup2(fd, STDOUT_FILENO);
-	ast_close_fds_above_n(STDERR_FILENO);
-	/* Most commonly installed in /usr/local/bin */
-	execl(NBSCAT, "nbscat8k", "-d", (char *)NULL);
-	execl(LOCAL_NBSCAT, "nbscat8k", "-d", (char *)NULL);
-	fprintf(stderr, "Execute of nbscat8k failed\n");
-	_exit(0);
-}
-
-static int timed_read(int fd, void *data, int datalen)
-{
-	int res;
-	struct pollfd fds[1];
-	fds[0].fd = fd;
-	fds[0].events = POLLIN;
-	res = ast_poll(fds, 1, 2000);
-	if (res < 1) {
-		ast_log(LOG_NOTICE, "Selected timed out/errored out with %d\n", res);
-		return -1;
-	}
-	return read(fd, data, datalen);
-
-}
-
-static int NBScat_exec(struct ast_channel *chan, const char *data)
-{
-	int res=0;
-	int fds[2];
-	int ms = -1;
-	int pid = -1;
-	struct ast_format *owriteformat;
-	struct timeval next;
-	struct ast_frame *f;
-	struct myframe {
-		struct ast_frame f;
-		char offset[AST_FRIENDLY_OFFSET];
-		short frdata[160];
-	} myf;
-
-	if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds)) {
-		ast_log(LOG_WARNING, "Unable to create socketpair\n");
-		return -1;
-	}
-
-	ast_stopstream(chan);
-
-	owriteformat = ao2_bump(ast_channel_writeformat(chan));
-	res = ast_set_write_format(chan, ast_format_slin);
-	if (res < 0) {
-		ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
-		ao2_cleanup(owriteformat);
-		return -1;
-	}
-
-	myf.f.frametype = AST_FRAME_VOICE;
-	myf.f.subclass.format = ast_format_slin;
-	myf.f.mallocd = 0;
-	myf.f.offset = AST_FRIENDLY_OFFSET;
-	myf.f.src = __PRETTY_FUNCTION__;
-	myf.f.delivery.tv_sec = 0;
-	myf.f.delivery.tv_usec = 0;
-	myf.f.data.ptr = myf.frdata;
-
-	res = NBScatplay(fds[1]);
-	/* Wait 1000 ms first */
-	next = ast_tvnow();
-	next.tv_sec += 1;
-	if (res >= 0) {
-		pid = res;
-		/* Order is important -- there's almost always going to be mp3...  we want to prioritize the
-		   user */
-		for (;;) {
-			ms = ast_tvdiff_ms(next, ast_tvnow());
-			if (ms <= 0) {
-				res = timed_read(fds[0], myf.frdata, sizeof(myf.frdata));
-				if (res > 0) {
-					myf.f.datalen = res;
-					myf.f.samples = res / 2;
-					if (ast_write(chan, &myf.f) < 0) {
-						res = -1;
-						break;
-					}
-				} else {
-					ast_debug(1, "No more mp3\n");
-					res = 0;
-					break;
-				}
-				next = ast_tvadd(next, ast_samp2tv(myf.f.samples, 8000));
-			} else {
-				ms = ast_waitfor(chan, ms);
-				if (ms < 0) {
-					ast_debug(1, "Hangup detected\n");
-					res = -1;
-					break;
-				}
-				if (ms) {
-					f = ast_read(chan);
-					if (!f) {
-						ast_debug(1, "Null frame == hangup() detected\n");
-						res = -1;
-						break;
-					}
-					if (f->frametype == AST_FRAME_DTMF) {
-						ast_debug(1, "User pressed a key\n");
-						ast_frfree(f);
-						res = 0;
-						break;
-					}
-					ast_frfree(f);
-				}
-			}
-		}
-	}
-	close(fds[0]);
-	close(fds[1]);
-	ast_frfree(&myf.f);
-
-	if (pid > -1)
-		kill(pid, SIGKILL);
-	if (!res && owriteformat)
-		ast_set_write_format(chan, owriteformat);
-	ao2_cleanup(owriteformat);
-
-	return res;
-}
-
-static int unload_module(void)
-{
-	return ast_unregister_application(app);
-}
-
-static int load_module(void)
-{
-	return ast_register_application_xml(app, NBScat_exec);
-}
-
-AST_MODULE_INFO_STANDARD_DEPRECATED(ASTERISK_GPL_KEY, "Silly NBS Stream Application");
diff --git a/apps/app_osplookup.c b/apps/app_osplookup.c
index 28d492255f222210dfce3143948b1834fd204843..f80324f80a62e578c0dc8fe7b02c8f4706060553 100644
--- a/apps/app_osplookup.c
+++ b/apps/app_osplookup.c
@@ -31,7 +31,8 @@
 /*** MODULEINFO
 	<depend>osptk</depend>
 	<depend>openssl</depend>
-	<support_level>extended</support_level>
+	<defaultenabled>no</defaultenabled>
+	<support_level>deprecated</support_level>
 	<deprecated_in>19</deprecated_in>
 	<removed_in>21</removed_in>
  ***/
@@ -3165,7 +3166,7 @@ static int reload(void)
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Open Settlement Protocol Applications",
-	.support_level = AST_MODULE_SUPPORT_EXTENDED,
+	.support_level = AST_MODULE_SUPPORT_DEPRECATED,
 	.load = load_module,
 	.unload = unload_module,
 	.reload = reload,
diff --git a/apps/app_playback.c b/apps/app_playback.c
index 56e74ac5e89c253c85675730e83275639595fc49..afa959be00582553b94d868f86b29ae6c4f1347a 100644
--- a/apps/app_playback.c
+++ b/apps/app_playback.c
@@ -73,8 +73,8 @@
 		</syntax>
 		<description>
 			<para>Plays back given filenames (do not put extension of wav/alaw etc).
-			The playback command answer the channel if no options are specified.
-			If the file is non-existant it will fail</para>
+			The Playback application answers the channel if no options are specified.
+			If the file is non-existent it will fail.</para>
 			<para>This application sets the following channel variable upon completion:</para>
 			<variablelist>
 				<variable name="PLAYBACKSTATUS">
@@ -507,8 +507,7 @@ static int playback_exec(struct ast_channel *chan, const char *data)
 			if (!res) {
 				res = ast_waitstream(chan, "");
 				ast_stopstream(chan);
-			}
-			if (res) {
+			} else {
 				if (!ast_check_hangup(chan)) {
 					ast_log(LOG_WARNING, "Playback failed on %s for %s\n", ast_channel_name(chan), (char *)data);
 				}
diff --git a/apps/app_queue.c b/apps/app_queue.c
index 35054130a437d00fae694a1a65fcc1a8efe963e0..31d63aa28093daf33668f70fe9a330b2e92d93fb 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -161,7 +161,10 @@
 						<para>Continue in the dialplan if the callee hangs up.</para>
 					</option>
 					<option name="d">
-						<para>data-quality (modem) call (minimum delay).</para>
+						<para>Data-quality (modem) call (minimum delay).</para>
+						<para>This option only applies to DAHDI channels. By default,
+						DTMF is verified by muting audio TX/RX to verify the tone
+						is still present. This option disables that behavior.</para>
 					</option>
 					<option name="F" argsep="^">
 						<argument name="context" required="false" />
@@ -171,12 +174,6 @@
 						to the specified destination and <emphasis>start</emphasis> execution at that location.</para>
 						<para>NOTE: Any channel variables you want the called channel to inherit from the caller channel must be
 						prefixed with one or two underbars ('_').</para>
-					</option>
-					<option name="F">
-						<para>When the caller hangs up, transfer the <emphasis>called member</emphasis> to the next priority of
-						the current extension and <emphasis>start</emphasis> execution at that location.</para>
-						<para>NOTE: Any channel variables you want the called channel to inherit from the caller channel must be
-						prefixed with one or two underbars ('_').</para>
 						<para>NOTE: Using this option from a Macro() or GoSub() might not make sense as there would be no return points.</para>
 					</option>
 					<option name="h">
@@ -185,10 +182,6 @@
 					<option name="H">
 						<para>Allow <emphasis>caller</emphasis> to hang up by pressing <literal>*</literal>.</para>
 					</option>
-					<option name="n">
-						<para>No retries on the timeout; will exit this application and
-						go to the next step.</para>
-					</option>
 					<option name="i">
 						<para>Ignore call forward requests from queue members and do nothing
 						when they are requested.</para>
@@ -197,6 +190,23 @@
 						<para>Asterisk will ignore any connected line update requests or any redirecting party
 						update requests it may receive on this dial attempt.</para>
 					</option>
+					<option name="k">
+						<para>Allow the <emphasis>called</emphasis> party to enable parking of the call by sending
+						the DTMF sequence defined for call parking in <filename>features.conf</filename>.</para>
+					</option>
+					<option name="K">
+						<para>Allow the <emphasis>calling</emphasis> party to enable parking of the call by sending
+						the DTMF sequence defined for call parking in <filename>features.conf</filename>.</para>
+					</option>
+					<option name="m">
+						<para>Custom music on hold class to use, which will override the music on hold class configured
+						in <filename>queues.conf</filename>, if specified.</para>
+						<para>Note that CHANNEL(musicclass), if set, will still override this option.</para>
+					</option>
+					<option name="n">
+						<para>No retries on the timeout; will exit this application and
+						go to the next step.</para>
+					</option>
 					<option name="r">
 						<para>Ring instead of playing MOH. Periodic Announcements are still made, if applicable.</para>
 					</option>
@@ -217,14 +227,6 @@
 						<para>Allow the <emphasis>calling</emphasis> user to write the conversation to
 						disk via Monitor.</para>
 					</option>
-					<option name="k">
-						<para>Allow the <emphasis>called</emphasis> party to enable parking of the call by sending
-						the DTMF sequence defined for call parking in <filename>features.conf</filename>.</para>
-					</option>
-					<option name="K">
-						<para>Allow the <emphasis>calling</emphasis> party to enable parking of the call by sending
-						the DTMF sequence defined for call parking in <filename>features.conf</filename>.</para>
-					</option>
 					<option name="x">
 						<para>Allow the <emphasis>called</emphasis> user to write the conversation
 						to disk via MixMonitor.</para>
@@ -1272,7 +1274,7 @@
 			</syntax>
 			<see-also>
 				<ref type="application">PauseQueueMember</ref>
-				<ref type="application">UnPauseQueueMember</ref>
+				<ref type="application">UnpauseQueueMember</ref>
 			</see-also>
 		</managerEventInstance>
 	</managerEvent>
@@ -1468,12 +1470,14 @@ enum {
 	OPT_CALLER_AUTOMON =         (1 << 18),
 	OPT_PREDIAL_CALLEE =         (1 << 19),
 	OPT_PREDIAL_CALLER =         (1 << 20),
+	OPT_MUSICONHOLD_CLASS =      (1 << 21),
 };
 
 enum {
 	OPT_ARG_CALLEE_GO_ON = 0,
 	OPT_ARG_PREDIAL_CALLEE,
 	OPT_ARG_PREDIAL_CALLER,
+	OPT_ARG_MUSICONHOLD_CLASS,
 	/* note: this entry _MUST_ be the last one in the enum */
 	OPT_ARG_ARRAY_SIZE
 };
@@ -1491,6 +1495,7 @@ AST_APP_OPTIONS(queue_exec_options, BEGIN_OPTIONS
 	AST_APP_OPTION('I', OPT_IGNORE_CONNECTEDLINE),
 	AST_APP_OPTION('k', OPT_CALLEE_PARK),
 	AST_APP_OPTION('K', OPT_CALLER_PARK),
+	AST_APP_OPTION_ARG('m', OPT_MUSICONHOLD_CLASS, OPT_ARG_MUSICONHOLD_CLASS),
 	AST_APP_OPTION('n', OPT_NO_RETRY),
 	AST_APP_OPTION('r', OPT_RINGING),
 	AST_APP_OPTION('R', OPT_RING_WHEN_RINGING),
@@ -2948,7 +2953,11 @@ static void init_queue(struct call_queue *q)
 	q->timeout = DEFAULT_TIMEOUT;
 	q->maxlen = 0;
 
+	ast_string_field_set(q, announce, "");
 	ast_string_field_set(q, context, "");
+	ast_string_field_set(q, membermacro, "");
+	ast_string_field_set(q, membergosub, "");
+	ast_string_field_set(q, defaultrule, "");
 
 	q->announcefrequency = 0;
 	q->minannouncefrequency = DEFAULT_MIN_ANNOUNCE_FREQUENCY;
@@ -2977,7 +2986,10 @@ static void init_queue(struct call_queue *q)
 	q->periodicannouncefrequency = 0;
 	q->randomperiodicannounce = 0;
 	q->numperiodicannounce = 0;
+	q->relativeperiodicannounce = 0;
 	q->autopause = QUEUE_AUTOPAUSE_OFF;
+	q->autopausebusy = 0;
+	q->autopauseunavail = 0;
 	q->timeoutpriority = TIMEOUT_PRIORITY_APP;
 	q->autopausedelay = 0;
 	if (!q->members) {
@@ -3002,6 +3014,7 @@ static void init_queue(struct call_queue *q)
 	ast_string_field_set(q, sound_minute, "queue-minute");
 	ast_string_field_set(q, sound_seconds, "queue-seconds");
 	ast_string_field_set(q, sound_thanks, "queue-thankyou");
+	ast_string_field_set(q, sound_callerannounce, "");
 	ast_string_field_set(q, sound_reporthold, "queue-reporthold");
 
 	if (!q->sound_periodicannounce[0]) {
@@ -8164,7 +8177,7 @@ static int pqm_exec(struct ast_channel *chan, const char *data)
 	return 0;
 }
 
-/*! \brief UnPauseQueueMember application */
+/*! \brief UnpauseQueueMember application */
 static int upqm_exec(struct ast_channel *chan, const char *data)
 {
 	char *parse;
@@ -8185,7 +8198,7 @@ static int upqm_exec(struct ast_channel *chan, const char *data)
 	AST_STANDARD_APP_ARGS(args, parse);
 
 	if (ast_strlen_zero(args.interface)) {
-		ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename],interface[,options[,reason]])\n");
+		ast_log(LOG_WARNING, "Missing interface argument to UnpauseQueueMember ([queuename],interface[,options[,reason]])\n");
 		return -1;
 	}
 
@@ -8625,6 +8638,12 @@ static int queue_exec(struct ast_channel *chan, const char *data)
 		ast_app_exec_sub(NULL, chan, opt_args[OPT_ARG_PREDIAL_CALLER], 0);
 	}
 
+	/* Music on hold class override */
+	if (ast_test_flag(&opts, OPT_MUSICONHOLD_CLASS)
+		&& !ast_strlen_zero(opt_args[OPT_ARG_MUSICONHOLD_CLASS])) {
+		ast_copy_string(qe.moh, opt_args[OPT_ARG_MUSICONHOLD_CLASS], sizeof(qe.moh));
+	}
+
 	copy_rules(&qe, args.rule);
 	qe.pr = AST_LIST_FIRST(&qe.qe_rules);
 check_turns:
diff --git a/apps/app_read.c b/apps/app_read.c
index f4a965c8b46d36b64772a2803f4e677467b20d44..e2ac60cc96a1ef33511309b688df275a609ae980 100644
--- a/apps/app_read.c
+++ b/apps/app_read.c
@@ -85,6 +85,13 @@
 						and you will need to rely on duration and max digits
 						for ending input.</para>
 					</option>
+					<option name="e">
+						<para>to read the terminator as the digit string if the
+						only digit read is the terminator. This is for cases
+						where the terminator is a valid digit, but only by itself.
+						ie; <literal>1234</literal> and <literal>#</literal> are
+						valid, but <literal>1234#</literal> is not.</para>
+					</option>
 				</optionlist>
 			</parameter>
 			<parameter name="attempts">
@@ -125,6 +132,7 @@ enum read_option_flags {
 	OPT_INDICATION = (1 << 1),
 	OPT_NOANSWER = (1 << 2),
 	OPT_TERMINATOR = (1 << 3),
+	OPT_KEEP_TERMINATOR = (1 << 4),
 };
 
 enum {
@@ -138,6 +146,7 @@ AST_APP_OPTIONS(read_app_options, {
 	AST_APP_OPTION('i', OPT_INDICATION),
 	AST_APP_OPTION('n', OPT_NOANSWER),
 	AST_APP_OPTION_ARG('t', OPT_TERMINATOR, OPT_ARG_TERMINATOR),
+	AST_APP_OPTION('e', OPT_KEEP_TERMINATOR),
 });
 
 static char *app = "Read";
@@ -261,12 +270,20 @@ static int read_exec(struct ast_channel *chan, const char *data)
 				}
 			} else {
 				res = ast_app_getdata_terminator(chan, arglist.filename, tmp, maxdigits, to, terminator);
-				if (res == AST_GETDATA_COMPLETE || res == AST_GETDATA_EMPTY_END_TERMINATED)
+				if (res == AST_GETDATA_COMPLETE) {
+					status = "OK";
+				} else if (res == AST_GETDATA_EMPTY_END_TERMINATED) {
+					if (ast_test_flag(&flags, OPT_KEEP_TERMINATOR)) {
+						/* if the option is set to do so, read the
+						   returned string as the terminator string */
+						ast_copy_string(tmp, terminator, sizeof(tmp));
+					}
 					status = "OK";
-				else if (res == AST_GETDATA_TIMEOUT)
+				} else if (res == AST_GETDATA_TIMEOUT) {
 					status = "TIMEOUT";
-				else if (res == AST_GETDATA_INTERRUPTED)
+				} else if (res == AST_GETDATA_INTERRUPTED) {
 					status = "INTERRUPTED";
+				}
 			}
 			if (res > -1) {
 				pbx_builtin_setvar_helper(chan, arglist.variable, tmp);
diff --git a/apps/app_senddtmf.c b/apps/app_senddtmf.c
index ea75a9e5fb821bced7fc6d5f27c8a567c08116c6..ed548094db35b64d1ef3887a79af0706420c9aaf 100644
--- a/apps/app_senddtmf.c
+++ b/apps/app_senddtmf.c
@@ -57,6 +57,15 @@
 			<parameter name="channel" required="false">
 				<para>Channel where digits will be played</para>
 			</parameter>
+			<parameter name="options">
+				<optionlist>
+					<option name="a">
+						<para>Answer the channel specified by the <literal>channel</literal>
+						parameter if it is not already up. If no <literal>channel</literal>
+						parameter is provided, the current channel will be answered.</para>
+					</option>
+				</optionlist>
+			</parameter>
 		</syntax>
 		<description>
 			<para>It will send all digits or terminate if it encounters an error.</para>
@@ -88,8 +97,38 @@
 			<para>Plays a dtmf digit on the specified channel.</para>
 		</description>
 	</manager>
+	<manager name="SendFlash" language="en_US">
+		<synopsis>
+			Send a hook flash on a specific channel.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="Channel" required="true">
+				<para>Channel name to send hook flash to.</para>
+			</parameter>
+			<parameter name="Receive" required="false">
+				<para>Emulate receiving a hook flash on this channel instead of sending it out.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Sends a hook flash on the specified channel.</para>
+		</description>
+	</manager>
  ***/
 
+enum read_option_flags {
+	OPT_ANSWER = (1 << 0),
+};
+
+AST_APP_OPTIONS(senddtmf_app_options, {
+	AST_APP_OPTION('a', OPT_ANSWER),
+});
+
+enum {
+	/* note: this entry _MUST_ be the last one in the enum */
+	OPT_ARG_ARRAY_SIZE,
+};
+
 static const char senddtmf_name[] = "SendDTMF";
 
 static int senddtmf_exec(struct ast_channel *chan, const char *vdata)
@@ -100,11 +139,14 @@ static int senddtmf_exec(struct ast_channel *chan, const char *vdata)
 	struct ast_channel *chan_found = NULL;
 	struct ast_channel *chan_dest = chan;
 	struct ast_channel *chan_autoservice = NULL;
+	char *opt_args[OPT_ARG_ARRAY_SIZE];
+	struct ast_flags flags = {0};
 	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(digits);
 		AST_APP_ARG(dinterval);
 		AST_APP_ARG(duration);
 		AST_APP_ARG(channel);
+		AST_APP_ARG(options);
 	);
 
 	if (ast_strlen_zero(vdata)) {
@@ -136,6 +178,12 @@ static int senddtmf_exec(struct ast_channel *chan, const char *vdata)
 			chan_autoservice = chan;
 		}
 	}
+	if (!ast_strlen_zero(args.options)) {
+		ast_app_parse_options(senddtmf_app_options, &flags, opt_args, args.options);
+	}
+	if (ast_test_flag(&flags, OPT_ANSWER)) {
+		ast_auto_answer(chan_dest);
+	}
 	res = ast_dtmf_stream(chan_dest, chan_autoservice, args.digits,
 		dinterval <= 0 ? 250 : dinterval, duration);
 	if (chan_found) {
@@ -187,12 +235,41 @@ static int manager_play_dtmf(struct mansession *s, const struct message *m)
 	return 0;
 }
 
+static int manager_send_flash(struct mansession *s, const struct message *m)
+{
+	const char *channel = astman_get_header(m, "Channel");
+	const char *receive_s = astman_get_header(m, "Receive");
+	struct ast_channel *chan;
+
+	if (!(chan = ast_channel_get_by_name(channel))) {
+		astman_send_error(s, m, "Channel not found");
+		return 0;
+	}
+
+	if (ast_true(receive_s)) {
+		struct ast_frame f = { AST_FRAME_CONTROL, };
+		f.subclass.integer = AST_CONTROL_FLASH;
+		ast_queue_frame(chan, &f);
+	} else {
+		struct ast_frame f = { AST_FRAME_CONTROL, };
+		f.subclass.integer = AST_CONTROL_FLASH;
+		ast_channel_lock(chan);
+		ast_write(chan, &f);
+		ast_channel_unlock(chan);
+	}
+
+	chan = ast_channel_unref(chan);
+	astman_send_ack(s, m, "Flash successfully queued");
+	return 0;
+}
+
 static int unload_module(void)
 {
 	int res;
 
 	res = ast_unregister_application(senddtmf_name);
 	res |= ast_manager_unregister("PlayDTMF");
+	res |= ast_manager_unregister("SendFlash");
 
 	return res;
 }
@@ -202,6 +279,7 @@ static int load_module(void)
 	int res;
 
 	res = ast_manager_register_xml("PlayDTMF", EVENT_FLAG_CALL, manager_play_dtmf);
+	res |= ast_manager_register_xml("SendFlash", EVENT_FLAG_CALL, manager_send_flash);
 	res |= ast_register_application_xml(senddtmf_name, senddtmf_exec);
 
 	return res;
diff --git a/apps/app_sendtext.c b/apps/app_sendtext.c
index 07e6accff95e293c14aa16a7ae0f82c0d8023d3a..ee4231696574bd0719ccc02288d302575c8da9ae 100644
--- a/apps/app_sendtext.c
+++ b/apps/app_sendtext.c
@@ -139,12 +139,15 @@
 			</example>
 		</description>
 		<see-also>
-			<ref type="application">SendImage</ref>
-			<ref type="application">SendURL</ref>
 			<ref type="application">ReceiveText</ref>
 		</see-also>
 	</application>
 	<application name="ReceiveText" language="en_US">
+		<since>
+			<version>16.24.0</version>
+			<version>18.10.0</version>
+			<version>19.2.0</version>
+		</since>
 		<synopsis>
 			Receive a Text Message on a channel.
 		</synopsis>
@@ -177,8 +180,6 @@
 		</description>
 		<see-also>
 			<ref type="application">SendText</ref>
-			<ref type="application">SendImage</ref>
-			<ref type="application">SendURL</ref>
 		</see-also>
 	</application>
  ***/
diff --git a/apps/app_sf.c b/apps/app_sf.c
index dadc9cce52eba4e170b20c9b2e19da5b7801fd1c..cd8f79a6bc2cd966499017036eca60377b68a371 100644
--- a/apps/app_sf.c
+++ b/apps/app_sf.c
@@ -42,6 +42,11 @@
 
 /*** DOCUMENTATION
 	<application name="ReceiveSF" language="en_US">
+		<since>
+			<version>16.24.0</version>
+			<version>18.10.0</version>
+			<version>19.2.0</version>
+		</since>
 		<synopsis>
 			Detects SF digits on a channel and saves them to a variable.
 		</synopsis>
@@ -107,6 +112,11 @@
 		</see-also>
 	</application>
 	<application name="SendSF" language="en_US">
+		<since>
+			<version>16.24.0</version>
+			<version>18.10.0</version>
+			<version>19.2.0</version>
+		</since>
 		<synopsis>
 			Sends arbitrary SF digits on the current or specified channel.
 		</synopsis>
@@ -155,6 +165,21 @@ AST_APP_OPTIONS(read_app_options, {
 static const char *readsf_name = "ReceiveSF";
 static const char sendsf_name[] = "SendSF";
 
+/*!
+ * \brief Detects SF digits on channel using DSP
+ *
+ * \param chan channel on which to read digits
+ * \param buf Buffer in which to store digits
+ * \param buflen Size of buffer
+ * \param timeout ms to wait for all digits before giving up
+ * \param maxdigits Maximum number of digits
+ * \param freq Frequency to use
+ * \param features DSP features
+ * \param extrapulses Whether to recognize extra pulses
+ *
+ * \retval 0 if successful
+ * \retval -1 if unsuccessful (including hangup).
+ */
 static int read_sf_digits(struct ast_channel *chan, char *buf, int buflen, int timeout, int maxdigits, int freq, int features, int extrapulses) {
 	/* Bell System Technical Journal 39 (Nov. 1960) */
 	#define SF_MIN_OFF 25
@@ -169,6 +194,7 @@ static int read_sf_digits(struct ast_channel *chan, char *buf, int buflen, int t
 	char *str = buf;
 	int hits = 0, digits_read = 0;
 	unsigned short int sf_on = 0;
+	int res = 0;
 
 	if (!(dsp = ast_dsp_new())) {
 		ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
@@ -261,7 +287,7 @@ static int read_sf_digits(struct ast_channel *chan, char *buf, int buflen, int t
 								ast_debug(2, "Got more than 10 pulses, truncating to 10\n");
 								hits = 0; /* 10 dial pulses = digit 0 */
 								*str++ = hits + '0';
-								}
+							}
 						} else {
 							if (hits == 10) {
 								hits = 0; /* 10 dial pulses = digit 0 */
@@ -281,13 +307,14 @@ static int read_sf_digits(struct ast_channel *chan, char *buf, int buflen, int t
 			ast_frfree(frame);
 		} else {
 			pbx_builtin_setvar_helper(chan, "RECEIVESFSTATUS", "HANGUP");
+			res = -1;
 		}
 	}
 	if (dsp) {
 		ast_dsp_free(dsp);
 	}
 	ast_debug(3, "channel '%s' - event loop stopped { timeout: %d, remaining_time: %d }\n", ast_channel_name(chan), timeout, remaining_time);
-	return 0;
+	return res;
 }
 
 static int read_sf_exec(struct ast_channel *chan, const char *data)
@@ -297,7 +324,7 @@ static int read_sf_exec(struct ast_channel *chan, const char *data)
 	double tosec;
 	struct ast_flags flags = {0};
 	char *argcopy = NULL;
-	int features = 0, digits = 0, to = 0, freq = 2600;
+	int res, features = 0, digits = 0, to = 0, freq = 2600;
 
 	AST_DECLARE_APP_ARGS(arglist,
 		AST_APP_ARG(variable);
@@ -360,14 +387,14 @@ static int read_sf_exec(struct ast_channel *chan, const char *data)
 		features |= DSP_DIGITMODE_RELAXDTMF;
 	}
 
-	read_sf_digits(chan, tmp, BUFFER_SIZE, to, digits, freq, features, ast_test_flag(&flags, OPT_EXTRAPULSES));
+	res = read_sf_digits(chan, tmp, BUFFER_SIZE, to, digits, freq, features, ast_test_flag(&flags, OPT_EXTRAPULSES));
 	pbx_builtin_setvar_helper(chan, arglist.variable, tmp);
 	if (!ast_strlen_zero(tmp)) {
 		ast_verb(3, "SF digits received: '%s'\n", tmp);
-	} else {
+	} else if (!res) { /* if channel hung up, don't print anything out */
 		ast_verb(3, "No SF digits received.\n");
 	}
-	return 0;
+	return res;
 }
 
 static int sendsf_exec(struct ast_channel *chan, const char *vdata)
diff --git a/apps/app_signal.c b/apps/app_signal.c
new file mode 100644
index 0000000000000000000000000000000000000000..a61bc61b3a4656dd14dd6fa401125dd5d6fba1e3
--- /dev/null
+++ b/apps/app_signal.c
@@ -0,0 +1,471 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Naveen Albert
+ *
+ * Naveen Albert <asterisk@phreaknet.org>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Channel signaling applications
+ *
+ * \author Naveen Albert <asterisk@phreaknet.org>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+	<support_level>extended</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/file.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/app.h"
+#include "asterisk/module.h"
+
+/*** DOCUMENTATION
+	<application name="Signal" language="en_US">
+		<synopsis>
+			Sends a signal to any waiting channels.
+		</synopsis>
+		<syntax>
+			<parameter name="signalname" required="true">
+				<para>Name of signal to send.</para>
+			</parameter>
+			<parameter name="payload" required="false">
+				<para>Payload data to deliver.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Sends a named signal to any channels that may be
+			waiting for one. Acts as a producer in a simple
+			message queue.</para>
+			<variablelist>
+				<variable name="SIGNALSTATUS">
+					<value name="SUCCESS">
+						Signal was successfully sent to at least
+						one listener for processing.
+					</value>
+					<value name="FAILURE">
+						Signal could not be sent or nobody
+						was listening for this signal.
+					</value>
+				</variable>
+			</variablelist>
+			<example title="Send a signal named workdone">
+			same => n,Signal(workdone,Work has completed)
+			</example>
+		</description>
+		<see-also>
+			<ref type="application">WaitForSignal</ref>
+		</see-also>
+	</application>
+	<application name="WaitForSignal" language="en_US">
+		<synopsis>
+			Waits for a named signal on a channel.
+		</synopsis>
+		<syntax>
+			<parameter name="signalname" required="true">
+				<para>Name of signal to send.</para>
+			</parameter>
+			<parameter name="signaltimeout" required="false">
+				<para>Maximum time, in seconds, to wait for signal.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Waits for <replaceable>signaltimeout</replaceable> seconds on the current
+			channel to receive a signal with name <replaceable>signalname</replaceable>.
+			Acts as a consumer in a simple message queue.</para>
+			<para>Result of signal wait will be stored in the following variables:</para>
+			<variablelist>
+				<variable name="WAITFORSIGNALSTATUS">
+					<value name="SIGNALED">
+						Signal was received.
+					</value>
+					<value name="TIMEOUT">
+						Timed out waiting for signal.
+					</value>
+					<value name="HANGUP">
+						Channel hung up before signal was received.
+					</value>
+				</variable>
+				<variable name="WAITFORSIGNALPAYLOAD">
+					<para>Data payload attached to signal, if it exists</para>
+				</variable>
+			</variablelist>
+			<example title="Wait for the workdone signal, indefinitely, and print out payload">
+			same => n,WaitForSignal(workdone)
+			same => n,NoOp(Received: ${WAITFORSIGNALPAYLOAD})
+			</example>
+		</description>
+		<see-also>
+			<ref type="application">Signal</ref>
+		</see-also>
+	</application>
+ ***/
+
+static const char * const app = "Signal";
+static const char * const app2 = "WaitForSignal";
+
+struct signalitem {
+	ast_mutex_t lock;
+	char name[AST_MAX_CONTEXT];
+	int sig_alert_pipe[2];
+	int watchers;
+	unsigned int signaled:1;
+	char *payload;
+	AST_LIST_ENTRY(signalitem) entry;		/*!< Next Signal item */
+};
+
+static AST_RWLIST_HEAD_STATIC(signals, signalitem);
+
+static struct signalitem *alloc_signal(const char *sname)
+{
+	struct signalitem *s;
+
+	if (!(s = ast_calloc(1, sizeof(*s)))) {
+		return NULL;
+	}
+
+	ast_mutex_init(&s->lock);
+	ast_copy_string(s->name, sname, sizeof(s->name));
+
+	s->sig_alert_pipe[0] = -1;
+	s->sig_alert_pipe[1] = -1;
+	s->watchers = 0;
+	s->payload = NULL;
+	ast_alertpipe_init(s->sig_alert_pipe);
+
+	return s;
+}
+
+static int dealloc_signal(struct signalitem *s)
+{
+	if (s->watchers) { /* somebody is still using us... refuse to go away */
+		ast_debug(1, "Signal '%s' is still being used by %d listener(s)\n", s->name, s->watchers);
+		return -1;
+	}
+	ast_alertpipe_close(s->sig_alert_pipe);
+	ast_mutex_destroy(&s->lock);
+	if (s->payload) {
+		ast_free(s->payload);
+		s->payload = NULL;
+	}
+	ast_free(s);
+	s = NULL;
+	return 0;
+}
+
+static int remove_signal(char *sname)
+{
+	int res = -1;
+	struct signalitem *s;
+
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&signals, s, entry) {
+		if (!strcmp(s->name, sname)) {
+			AST_LIST_REMOVE_CURRENT(entry);
+			res = dealloc_signal(s);
+			ast_debug(1, "Removed signal '%s'\n", sname);
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+
+	return res;
+}
+
+static struct signalitem *get_signal(char *sname, int addnew)
+{
+	struct signalitem *s = NULL;
+	AST_RWLIST_WRLOCK(&signals);
+	AST_LIST_TRAVERSE(&signals, s, entry) {
+		if (!strcasecmp(s->name, sname)) {
+			ast_debug(1, "Using existing signal item '%s'\n", sname);
+			break;
+		}
+	}
+	if (!s) {
+		if (addnew) { /* signal doesn't exist, so create it */
+			s = alloc_signal(sname);
+			/* Totally fail if we fail to find/create an entry */
+			if (s) {
+				ast_debug(1, "Created new signal item '%s'\n", sname);
+				AST_RWLIST_INSERT_HEAD(&signals, s, entry);
+			} else {
+				ast_log(LOG_WARNING, "Failed to create signal item for '%s'\n", sname);
+			}
+		} else {
+			ast_debug(1, "Signal '%s' doesn't exist, and not creating it\n", sname);
+		}
+	}
+	AST_RWLIST_UNLOCK(&signals);
+	return s;
+}
+
+static int wait_for_signal_or_hangup(struct ast_channel *chan, char *signame, int timeout)
+{
+	struct signalitem *s = NULL;
+	int ms, remaining_time, res = 1, goaway = 0;
+	struct timeval start;
+	struct ast_frame *frame = NULL;
+
+	remaining_time = timeout;
+	start = ast_tvnow();
+
+	s = get_signal(signame, 1);
+
+	ast_mutex_lock(&s->lock);
+	s->watchers = s->watchers + 1; /* we unlock, because a) other people need to use this and */
+	ast_mutex_unlock(&s->lock); /* b) the signal will be available to us as long as watchers > 0 */
+
+	while (timeout == 0 || remaining_time > 0) {
+		int ofd, exception;
+
+		ms = 1000;
+		errno = 0;
+		if (ast_waitfor_nandfds(&chan, 1, &s->sig_alert_pipe[0], 1, &exception, &ofd, &ms)) { /* channel won */
+			if (!(frame = ast_read(chan))) { /* channel hung up */
+				ast_debug(1, "Channel '%s' did not return a frame; probably hung up.\n", ast_channel_name(chan));
+				res = -1;
+				break;
+			} else {
+				ast_frfree(frame); /* handle frames */
+			}
+		} else if (ofd == s->sig_alert_pipe[0]) { /* fd won */
+			if (ast_alertpipe_read(s->sig_alert_pipe) == AST_ALERT_READ_SUCCESS) {
+				ast_debug(1, "Alert pipe has data for us\n");
+				res = 0;
+				break;
+			} else {
+				ast_debug(1, "Alert pipe does not have data for us\n");
+			}
+		} else { /* nobody won */
+			if (ms && (ofd < 0)) {
+				if (!((errno == 0) || (errno == EINTR))) {
+					ast_log(LOG_WARNING, "Something bad happened while channel '%s' was polling.\n", ast_channel_name(chan));
+					break;
+				}
+			} /* else, nothing happened */
+		}
+		if (timeout) {
+			remaining_time = ast_remaining_ms(start, timeout);
+		}
+	}
+
+	/* WRLOCK the list so that if we're going to destroy the signal now, nobody else can grab it before that happens. */
+	AST_RWLIST_WRLOCK(&signals);
+	ast_mutex_lock(&s->lock);
+	if (s->payload) {
+		pbx_builtin_setvar_helper(chan, "WAITFORSIGNALPAYLOAD", s->payload);
+	}
+	s->watchers = s->watchers - 1;
+	if (s->watchers) { /* folks are still waiting for this, pass it on... */
+		int save_errno = errno;
+		if (ast_alertpipe_write(s->sig_alert_pipe)) {
+			ast_log(LOG_WARNING, "%s: write() failed: %s\n", __FUNCTION__, strerror(errno));
+		}
+		errno = save_errno;
+	} else { /* nobody else is waiting for this */
+		goaway = 1; /* we were the last guy using this, so mark signal item for destruction */
+	}
+	ast_mutex_unlock(&s->lock);
+
+	if (goaway) {
+		/* remove_signal calls ast_mutex_destroy, so don't call it with the mutex itself locked. */
+		remove_signal(signame);
+	}
+	AST_RWLIST_UNLOCK(&signals);
+
+	return res;
+}
+
+static int send_signal(char *signame, char *payload)
+{
+	struct signalitem *s;
+	int save_errno = errno;
+	int res = 0;
+
+	s = get_signal(signame, 0); /* if signal doesn't exist already, no point in creating it, because nobody could be waiting for it! */
+
+	if (!s) {
+		return -1; /* this signal didn't exist, so we can't send a signal for it */
+	}
+
+	/* at this point, we know someone is listening, since signals are destroyed when watchers gets down to 0 */
+	ast_mutex_lock(&s->lock);
+	s->signaled = 1;
+	if (payload && *payload) {
+		int len = strlen(payload);
+		if (s->payload) {
+			ast_free(s->payload); /* if there was already a payload, replace it */
+			s->payload = NULL;
+		}
+		s->payload = ast_malloc(len + 1);
+		if (!s->payload) {
+			ast_log(LOG_WARNING, "Failed to allocate signal payload '%s'\n", payload);
+		} else {
+			ast_copy_string(s->payload, payload, len + 1);
+		}
+	}
+	if (ast_alertpipe_write(s->sig_alert_pipe)) {
+		ast_log(LOG_WARNING, "%s: write() failed: %s\n", __FUNCTION__, strerror(errno));
+		s->signaled = 0; /* okay, so we didn't send a signal after all... */
+		res = -1;
+	}
+	errno = save_errno;
+	ast_debug(1, "Sent '%s' signal to %d listeners\n", signame, s->watchers);
+	ast_mutex_unlock(&s->lock);
+
+	return res;
+}
+
+static int waitsignal_exec(struct ast_channel *chan, const char *data)
+{
+	char *argcopy;
+	int r = 0, timeoutms = 0;
+	double timeout = 0;
+
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(signame);
+		AST_APP_ARG(sigtimeout);
+	);
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "Signal() requires arguments\n");
+		return -1;
+	}
+
+	argcopy = ast_strdupa(data);
+	AST_STANDARD_APP_ARGS(args, argcopy);
+
+	if (ast_strlen_zero(args.signame)) {
+		ast_log(LOG_WARNING, "Missing signal name\n");
+		return -1;
+	}
+	if (strlen(args.signame) >= AST_MAX_CONTEXT) {
+		ast_log(LOG_WARNING, "Signal name '%s' is too long\n", args.signame);
+		return -1;
+	}
+	if (!ast_strlen_zero(args.sigtimeout)) {
+		if (sscanf(args.sigtimeout, "%30lg", &timeout) != 1 || timeout < 0) {
+			ast_log(LOG_WARNING, "Invalid timeout provided: %s. Defaulting to no timeout.\n", args.sigtimeout);
+		} else {
+			timeoutms = timeout * 1000; /* sec to msec */
+		}
+	}
+
+	if (timeout > 0) {
+		ast_debug(1, "Waiting for signal '%s' for %d ms\n", args.signame, timeoutms);
+	} else {
+		ast_debug(1, "Waiting for signal '%s', indefinitely\n", args.signame);
+	}
+
+	r = wait_for_signal_or_hangup(chan, args.signame, timeoutms);
+
+	if (r == 1) {
+		ast_verb(3, "Channel '%s' timed out, waiting for signal '%s'\n", ast_channel_name(chan), args.signame);
+		pbx_builtin_setvar_helper(chan, "WAITFORSIGNALSTATUS", "TIMEOUT");
+	} else if (!r) {
+		ast_verb(3, "Received signal '%s' on channel '%s'\n", args.signame, ast_channel_name(chan));
+		pbx_builtin_setvar_helper(chan, "WAITFORSIGNALSTATUS", "SIGNALED");
+	} else {
+		pbx_builtin_setvar_helper(chan, "WAITFORSIGNALSTATUS", "HANGUP");
+		ast_verb(3, "Channel '%s' hung up\n", ast_channel_name(chan));
+		return -1;
+	}
+
+	return 0;
+}
+
+static int signal_exec(struct ast_channel *chan, const char *data)
+{
+	char *argcopy;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(signame);
+		AST_APP_ARG(payload);
+	);
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "Signal() requires arguments\n");
+		return -1;
+	}
+
+	argcopy = ast_strdupa(data);
+	AST_STANDARD_APP_ARGS(args, argcopy);
+
+	if (ast_strlen_zero(args.signame)) {
+		ast_log(LOG_WARNING, "Missing signal name\n");
+		return -1;
+	}
+	if (strlen(args.signame) >= AST_MAX_CONTEXT) {
+		ast_log(LOG_WARNING, "Signal name '%s' is too long\n", args.signame);
+		return -1;
+	}
+
+	if (send_signal(args.signame, args.payload)) {
+		pbx_builtin_setvar_helper(chan, "SIGNALSTATUS", "FAILURE");
+	} else {
+		pbx_builtin_setvar_helper(chan, "SIGNALSTATUS", "SUCCESS");
+	}
+
+	return 0;
+}
+
+static int unload_module(void)
+{
+	struct signalitem *s;
+	int res = 0;
+
+	/* To avoid a locking nightmare, and for logistical reasons, this module
+	 * will refuse to unload if watchers > 0. That way we know a signal's
+	 * pipe won't disappear while it's being used. */
+
+	AST_RWLIST_WRLOCK(&signals);
+	/* Don't just use AST_RWLIST_REMOVE_HEAD, because if dealloc_signal fails, it should stay in the list. */
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&signals, s, entry) {
+		int mres = dealloc_signal(s);
+		res |= mres;
+		if (!mres) {
+			AST_LIST_REMOVE_CURRENT(entry);
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+	AST_RWLIST_UNLOCK(&signals);
+
+	/* One or more signals still has watchers. */
+	if (res) {
+		ast_log(LOG_WARNING, "One or more signals is currently in use. Unload failed.\n");
+		return res;
+	}
+
+	res |= ast_unregister_application(app);
+	res |= ast_unregister_application(app2);
+
+	return res;
+}
+
+static int load_module(void)
+{
+	int res;
+
+	res = ast_register_application_xml(app, signal_exec);
+	res |= ast_register_application_xml(app2, waitsignal_exec);
+
+	return res;
+}
+
+AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Channel Signaling Applications");
diff --git a/apps/app_stack.c b/apps/app_stack.c
index 557c6f160fb4a9a9b1a345ad4e5d8605739e3290..70aff6fcedbcd3c3a5c76063b8f1334eb79052d7 100644
--- a/apps/app_stack.c
+++ b/apps/app_stack.c
@@ -377,6 +377,26 @@ static int pop_exec(struct ast_channel *chan, const char *data)
 	return res;
 }
 
+static int frames_left(struct ast_channel *chan)
+{
+	struct ast_datastore *stack_store;
+	struct gosub_stack_list *oldlist;
+	int exists;
+
+	ast_channel_lock(chan);
+	stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
+	if (!stack_store) {
+		ast_channel_unlock(chan);
+		return -1;
+	}
+	oldlist = stack_store->data;
+	AST_LIST_LOCK(oldlist);
+	exists = oldlist->first ? 1 : 0;
+	AST_LIST_UNLOCK(oldlist);
+	ast_channel_unlock(chan);
+	return exists;
+}
+
 static int return_exec(struct ast_channel *chan, const char *data)
 {
 	struct ast_datastore *stack_store;
@@ -384,6 +404,7 @@ static int return_exec(struct ast_channel *chan, const char *data)
 	struct gosub_stack_list *oldlist;
 	const char *retval = data;
 	int res = 0;
+	int lastframe;
 
 	ast_channel_lock(chan);
 	if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) {
@@ -395,6 +416,7 @@ static int return_exec(struct ast_channel *chan, const char *data)
 	oldlist = stack_store->data;
 	AST_LIST_LOCK(oldlist);
 	oldframe = AST_LIST_REMOVE_HEAD(oldlist, entries);
+	lastframe = oldlist->first ? 0 : 1;
 	AST_LIST_UNLOCK(oldlist);
 
 	if (!oldframe) {
@@ -412,12 +434,19 @@ static int return_exec(struct ast_channel *chan, const char *data)
 	 * what was there before.  Channels that do not have a PBX may
 	 * not have the context or exten set.
 	 */
-	ast_channel_context_set(chan, oldframe->context);
-	ast_channel_exten_set(chan, oldframe->extension);
-	if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP)) {
-		--oldframe->priority;
+	if (ast_channel_pbx(chan) || !lastframe) {
+		/* If there's no PBX, the "old location" is simply
+		 * the configured context for the device, such as
+		 * for pre-dial handlers, and restoring this location
+		 * is nonsensical. So if no PBX and there are no further
+		 * frames, leave the location as it is. */
+		ast_channel_context_set(chan, oldframe->context);
+		ast_channel_exten_set(chan, oldframe->extension);
+		if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP)) {
+			--oldframe->priority;
+		}
+		ast_channel_priority_set(chan, oldframe->priority);
 	}
-	ast_channel_priority_set(chan, oldframe->priority);
 	ast_set2_flag(ast_channel_flags(chan), oldframe->in_subroutine, AST_FLAG_SUBROUTINE_EXEC);
 
 	gosub_release_frame(chan, oldframe);
@@ -1068,15 +1097,18 @@ static int gosub_run(struct ast_channel *chan, const char *sub_args, int ignore_
 				ast_channel_priority(chan), ast_channel_name(chan));
 		}
 
-		/* Did the routine return? */
-		if (ast_channel_priority(chan) == saved_priority
+		/* Did the routine return?
+		 * For things like predial where there's no PBX on the channel yet,
+		 * the last return leaves the location alone so we can print it out correctly here.
+		 * So to ensure we finished properly, make sure there are no frames left in that case. */
+		if ((!ast_channel_pbx(chan) && !frames_left(chan)) || (ast_channel_priority(chan) == saved_priority
 			&& !strcmp(ast_channel_context(chan), saved_context)
-			&& !strcmp(ast_channel_exten(chan), saved_exten)) {
+			&& !strcmp(ast_channel_exten(chan), saved_exten))) {
 			ast_verb(3, "%s Internal %s(%s) complete GOSUB_RETVAL=%s\n",
 				ast_channel_name(chan), app_gosub, sub_args,
 				S_OR(pbx_builtin_getvar_helper(chan, "GOSUB_RETVAL"), ""));
 		} else {
-			ast_log(LOG_NOTICE, "%s Abnormal '%s(%s)' exit.  Popping routine return locations.\n",
+			ast_log(LOG_WARNING, "%s Abnormal '%s(%s)' exit.  Popping routine return locations.\n",
 				ast_channel_name(chan), app_gosub, sub_args);
 			balance_stack(chan);
 			pbx_builtin_setvar_helper(chan, "GOSUB_RETVAL", "");
diff --git a/apps/app_url.c b/apps/app_url.c
deleted file mode 100644
index 08e1ec54c6939aba79c408f187cae8ac79f1ec2c..0000000000000000000000000000000000000000
--- a/apps/app_url.c
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2005, Digium, Inc.
- *
- * Mark Spencer <markster@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief App to transmit a URL
- *
- * \author Mark Spencer <markster@digium.com>
- *
- * \ingroup applications
- */
-
-/*** MODULEINFO
-	<support_level>deprecated</support_level>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include "asterisk.h"
-
-#include "asterisk/pbx.h"
-#include "asterisk/module.h"
-#include "asterisk/app.h"
-#include "asterisk/channel.h"
-
-/*** DOCUMENTATION
-	<application name="SendURL" language="en_US">
-		<synopsis>
-			Send a URL.
-		</synopsis>
-		<syntax>
-			<parameter name="URL" required="true" />
-			<parameter name="option">
-				<optionlist>
-					<option name="w">
-						<para>Execution will wait for an acknowledgement that the
-						URL has been loaded before continuing.</para>
-					</option>
-				</optionlist>
-			</parameter>
-		</syntax>
-		<description>
-			<para>Requests client go to <replaceable>URL</replaceable> (IAX2) or sends the
-			URL to the client (other channels).</para>
-			<para>Result is returned in the <variable>SENDURLSTATUS</variable> channel variable:</para>
-			<variablelist>
-				<variable name="SENDURLSTATUS">
-					<value name="SUCCESS">
-						URL successfully sent to client.
-					</value>
-					<value name="FAILURE">
-						Failed to send URL.
-					</value>
-					<value name="NOLOAD">
-						Client failed to load URL (wait enabled).
-					</value>
-					<value name="UNSUPPORTED">
-						Channel does not support URL transport.
-					</value>
-				</variable>
-			</variablelist>
-			<para>SendURL continues normally if the URL was sent correctly or if the channel
-			does not support HTML transport.  Otherwise, the channel is hung up.</para>
-		</description>
-		<see-also>
-			<ref type="application">SendImage</ref>
-			<ref type="application">SendText</ref>
-		</see-also>
-	</application>
- ***/
-
-static char *app = "SendURL";
-
-enum option_flags {
-	OPTION_WAIT = (1 << 0),
-};
-
-AST_APP_OPTIONS(app_opts,{
-	AST_APP_OPTION('w', OPTION_WAIT),
-});
-
-static int sendurl_exec(struct ast_channel *chan, const char *data)
-{
-	int res = 0;
-	char *tmp;
-	struct ast_frame *f;
-	char *status = "FAILURE";
-	char *opts[0];
-	struct ast_flags flags = { 0 };
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(url);
-		AST_APP_ARG(options);
-	);
-
-	if (ast_strlen_zero(data)) {
-		ast_log(LOG_WARNING, "SendURL requires an argument (URL)\n");
-		pbx_builtin_setvar_helper(chan, "SENDURLSTATUS", status);
-		return -1;
-	}
-
-	tmp = ast_strdupa(data);
-
-	AST_STANDARD_APP_ARGS(args, tmp);
-	if (args.argc == 2)
-		ast_app_parse_options(app_opts, &flags, opts, args.options);
-
-	if (!ast_channel_supports_html(chan)) {
-		/* Does not support transport */
-		pbx_builtin_setvar_helper(chan, "SENDURLSTATUS", "UNSUPPORTED");
-		return 0;
-	}
-	res = ast_channel_sendurl(chan, args.url);
-	if (res == -1) {
-		pbx_builtin_setvar_helper(chan, "SENDURLSTATUS", "FAILURE");
-		return res;
-	}
-	status = "SUCCESS";
-	if (ast_test_flag(&flags, OPTION_WAIT)) {
-		for(;;) {
-			/* Wait for an event */
-			res = ast_waitfor(chan, -1);
-			if (res < 0)
-				break;
-			f = ast_read(chan);
-			if (!f) {
-				res = -1;
-				status = "FAILURE";
-				break;
-			}
-			if (f->frametype == AST_FRAME_HTML) {
-				switch (f->subclass.integer) {
-				case AST_HTML_LDCOMPLETE:
-					res = 0;
-					ast_frfree(f);
-					status = "NOLOAD";
-					goto out;
-					break;
-				case AST_HTML_NOSUPPORT:
-					/* Does not support transport */
-					status = "UNSUPPORTED";
-					res = 0;
-					ast_frfree(f);
-					goto out;
-					break;
-				default:
-					ast_log(LOG_WARNING, "Don't know what to do with HTML subclass %d\n", f->subclass.integer);
-				};
-			}
-			ast_frfree(f);
-		}
-	}
-out:
-	pbx_builtin_setvar_helper(chan, "SENDURLSTATUS", status);
-	return res;
-}
-
-static int unload_module(void)
-{
-	return ast_unregister_application(app);
-}
-
-static int load_module(void)
-{
-	return ast_register_application_xml(app, sendurl_exec);
-}
-
-AST_MODULE_INFO_STANDARD_DEPRECATED(ASTERISK_GPL_KEY, "Send URL Applications");
diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c
index 49d30e09996befeed13af717dbd2ba05a70de16d..88dc342ee13d9fbce0c6aa384912bd62df1f0b1c 100644
--- a/apps/app_voicemail.c
+++ b/apps/app_voicemail.c
@@ -219,6 +219,10 @@
 						<para>Use the specified amount of gain when recording a voicemail message.
 						The units are whole-number decibels (dB).</para>
 					</option>
+					<option name="r">
+						<para>"Read only". Prevent user from deleting any messages.</para>
+						<para>This applies only to specific executions of VoiceMailMain, NOT the mailbox itself.</para>
+					</option>
 					<option name="s">
 						<para>Skip checking the passcode for the mailbox.</para>
 					</option>
@@ -566,6 +570,7 @@ static AST_LIST_HEAD_STATIC(vmstates, vmstate);
 #define VM_MOVEHEARD     (1 << 16)  /*!< Move a "heard" message to Old after listening to it */
 #define VM_MESSAGEWRAP   (1 << 17)  /*!< Wrap around from the last message to the first, and vice-versa */
 #define VM_FWDURGAUTO    (1 << 18)  /*!< Autoset of Urgent flag on forwarded Urgent messages set globally */
+#define VM_EMAIL_EXT_RECS (1 << 19)  /*!< Send voicemail emails when an external recording is added to a mailbox */
 #define ERROR_LOCK_PATH  -100
 #define ERROR_MAX_MSGS   -101
 #define OPERATOR_EXIT     300
@@ -592,6 +597,7 @@ enum vm_option_flags {
 	OPT_EARLYM_GREETING =  (1 << 10),
 	OPT_BEEP =             (1 << 11),
 	OPT_SILENT_IF_GREET =  (1 << 12),
+	OPT_READONLY =         (1 << 13),
 };
 
 enum vm_option_args {
@@ -621,7 +627,8 @@ AST_APP_OPTIONS(vm_app_options, {
 	AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
 	AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY),
 	AST_APP_OPTION('e', OPT_EARLYM_GREETING),
-	AST_APP_OPTION_ARG('t', OPT_BEEP, OPT_ARG_BEEP_TONE)
+	AST_APP_OPTION_ARG('t', OPT_BEEP, OPT_ARG_BEEP_TONE),
+	AST_APP_OPTION('r', OPT_READONLY),
 });
 
 static const char * const mailbox_folders[] = {
@@ -1252,6 +1259,8 @@ static void apply_option(struct ast_vm_user *vmu, const char *var, const char *v
 		ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
 	} else if (!strcasecmp(var, "attachfmt")) {
 		ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
+	} else if (!strcasecmp(var, "attachextrecs")) {
+		ast_set2_flag(vmu, ast_true(value), VM_EMAIL_EXT_RECS);
 	} else if (!strcasecmp(var, "serveremail")) {
 		ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
 	} else if (!strcasecmp(var, "fromstring")) {
@@ -4475,15 +4484,16 @@ static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxco
  */
 static int remove_file(char *dir, int msgnum)
 {
-	char fn[PATH_MAX];
-	char full_fn[PATH_MAX];
+	char fn[PATH_MAX] = "";
+	char full_fn[PATH_MAX + 4]; /* Plus .txt */
 	char msgnums[80];
 
 	if (msgnum > -1) {
 		snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
 		make_file(fn, sizeof(fn), dir, msgnum);
-	} else
+	} else {
 		ast_copy_string(fn, dir, sizeof(fn));
+	}
 	ast_filedelete(fn, NULL);
 	snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
 	unlink(full_fn);
@@ -6412,6 +6422,12 @@ static int msg_create_from_file(struct ast_vm_recording_data *recdata)
 	 * to do both with one line and is also safe to use with file storage mode. Also, if we are using ODBC, now is a good
 	 * time to create the voicemail database entry. */
 	if (ast_fileexists(destination, NULL, NULL) > 0) {
+		struct ast_channel *chan = NULL;
+		char fmt[80];
+		char clid[80];
+		char cidnum[80], cidname[80];
+		int send_email;
+
 		if (ast_check_realtime("voicemail_data")) {
 			get_date(date, sizeof(date));
 			ast_store_realtime("voicemail_data",
@@ -6431,7 +6447,27 @@ static int msg_create_from_file(struct ast_vm_recording_data *recdata)
 		}
 
 		STORE(dir, recipient->mailbox, recipient->context, msgnum, NULL, recipient, fmt, 0, vms, "", msg_id);
-		notify_new_state(recipient);
+
+		send_email = ast_test_flag(recipient, VM_EMAIL_EXT_RECS);
+
+		if (send_email) {
+			/* Send an email if possible, fall back to just notifications if not. */
+			ast_copy_string(fmt, recdata->recording_ext, sizeof(fmt));
+			ast_copy_string(clid, recdata->call_callerid, sizeof(clid));
+			ast_callerid_split(clid, cidname, sizeof(cidname), cidnum, sizeof(cidnum));
+
+			/* recdata->call_callerchan itself no longer exists, so we can't use the real channel. Use a dummy one. */
+			chan = ast_dummy_channel_alloc();
+		}
+		if (chan) {
+			notify_new_message(chan, recipient, NULL, msgnum, duration, fmt, cidnum, cidname, "");
+			ast_channel_unref(chan);
+		} else {
+			if (send_email) { /* We tried and failed. */
+				ast_log(LOG_WARNING, "Failed to allocate dummy channel, email will not be sent\n");
+			}
+			notify_new_state(recipient);
+		}
 	}
 
 	free_user(recipient);
@@ -10328,7 +10364,7 @@ static int vm_intro(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm
 	}
 }
 
-static int vm_instructions_en(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int skipadvanced, int in_urgent)
+static int vm_instructions_en(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int skipadvanced, int in_urgent, int nodelete)
 {
 	int res = 0;
 	/* Play instructions and wait for new command */
@@ -10388,10 +10424,12 @@ static int vm_instructions_en(struct ast_channel *chan, struct ast_vm_user *vmu,
 #ifdef IMAP_STORAGE
 				ast_mutex_unlock(&vms->lock);
 #endif
-				if (!curmsg_deleted) {
-					res = ast_play_and_wait(chan, "vm-delete");
-				} else {
-					res = ast_play_and_wait(chan, "vm-undelete");
+				if (!nodelete) {
+					if (!curmsg_deleted) {
+						res = ast_play_and_wait(chan, "vm-delete");
+					} else {
+						res = ast_play_and_wait(chan, "vm-undelete");
+					}
 				}
 				if (!res) {
 					res = ast_play_and_wait(chan, "vm-toforward");
@@ -10416,7 +10454,7 @@ static int vm_instructions_en(struct ast_channel *chan, struct ast_vm_user *vmu,
 	return res;
 }
 
-static int vm_instructions_ja(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms,  int skipadvanced, int in_urgent)
+static int vm_instructions_ja(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms,  int skipadvanced, int in_urgent, int nodelete)
 {
 	int res = 0;
 	/* Play instructions and wait for new command */
@@ -10512,7 +10550,7 @@ static int vm_instructions_ja(struct ast_channel *chan, struct ast_vm_user *vmu,
 	return res;
 }
 
-static int vm_instructions_zh(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms,  int skipadvanced, int in_urgent)
+static int vm_instructions_zh(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms,  int skipadvanced, int in_urgent, int nodelete)
 {
 	int res = 0;
 	/* Play instructions and wait for new command */
@@ -10530,20 +10568,20 @@ static int vm_instructions_zh(struct ast_channel *chan, struct ast_vm_user *vmu,
 			res = ast_play_and_wait(chan, "vm-opts");
 		if (!res) {
 			vms->starting = 0;
-			return vm_instructions_en(chan, vmu, vms, skipadvanced, in_urgent);
+			return vm_instructions_en(chan, vmu, vms, skipadvanced, in_urgent, nodelete);
 		}
 	}
 	return res;
 }
 
-static int vm_instructions(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int skipadvanced, int in_urgent)
+static int vm_instructions(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int skipadvanced, int in_urgent, int nodelete)
 {
 	if (!strncasecmp(ast_channel_language(chan), "ja", 2)) { /* Japanese syntax */
-		return vm_instructions_ja(chan, vmu, vms, skipadvanced, in_urgent);
+		return vm_instructions_ja(chan, vmu, vms, skipadvanced, in_urgent, nodelete);
 	} else if (vms->starting && !strncasecmp(ast_channel_language(chan), "zh", 2)) { /* CHINESE (Taiwan) syntax */
-		return vm_instructions_zh(chan, vmu, vms, skipadvanced, in_urgent);
+		return vm_instructions_zh(chan, vmu, vms, skipadvanced, in_urgent, nodelete);
 	} else {					/* Default to ENGLISH */
-		return vm_instructions_en(chan, vmu, vms, skipadvanced, in_urgent);
+		return vm_instructions_en(chan, vmu, vms, skipadvanced, in_urgent, nodelete);
 	}
 }
 
@@ -11189,12 +11227,14 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
 			password[0] = '\0';
 		} else {
 			if (ast_streamfile(chan, vm_password, ast_channel_language(chan))) {
-				ast_log(AST_LOG_WARNING, "Unable to stream password file\n");
+				if (!ast_check_hangup(chan)) {
+					ast_log(AST_LOG_WARNING, "Unable to stream password file\n");
+				}
 				free_user(vmu);
 				return -1;
 			}
 			if (ast_readstring(chan, password, sizeof(password) - 1, 2000, 10000, "#") < 0) {
-				ast_log(AST_LOG_WARNING, "Unable to read password\n");
+				ast_log(AST_LOG_NOTICE, "Unable to read password\n");
 				free_user(vmu);
 				return -1;
 			} else if (password[0] == '*') {
@@ -11426,6 +11466,7 @@ static int vm_execmain(struct ast_channel *chan, const char *data)
 	int play_auto = 0;
 	int play_folder = 0;
 	int in_urgent = 0;
+	int nodelete = 0;
 #ifdef IMAP_STORAGE
 	int deleted = 0;
 #endif
@@ -11488,6 +11529,9 @@ static int vm_execmain(struct ast_channel *chan, const char *data)
 					play_folder = 0;
 				}
 			}
+			if (ast_test_flag(&flags, OPT_READONLY)) {
+				nodelete = 1;
+			}
 		} else {
 			/* old style options parsing */
 			while (*(args.argv0)) {
@@ -11901,7 +11945,7 @@ static int vm_execmain(struct ast_channel *chan, const char *data)
 			}
 			break;
 		case '7': /* Delete the current message */
-			if (vms.curmsg >= 0 && vms.curmsg <= vms.lastmsg) {
+			if (!nodelete && vms.curmsg >= 0 && vms.curmsg <= vms.lastmsg) {
 				vms.deleted[vms.curmsg] = !vms.deleted[vms.curmsg];
 				if (useadsi)
 					adsi_delete(chan, &vms);
@@ -12090,7 +12134,7 @@ static int vm_execmain(struct ast_channel *chan, const char *data)
 					if (!cmd)
 						cmd = ast_play_and_wait(chan, "vm-opts");
 					if (!cmd)
-						cmd = vm_instructions(chan, vmu, &vms, 1, in_urgent);
+						cmd = vm_instructions(chan, vmu, &vms, 1, in_urgent, nodelete);
 					break;
 				}
 				cmd = ast_play_and_wait(chan, "vm-onefor");
@@ -12102,7 +12146,7 @@ static int vm_execmain(struct ast_channel *chan, const char *data)
 				if (!cmd)
 					cmd = ast_play_and_wait(chan, "vm-opts");
 				if (!cmd)
-					cmd = vm_instructions(chan, vmu, &vms, 1, in_urgent);
+					cmd = vm_instructions(chan, vmu, &vms, 1, in_urgent, nodelete);
 			} else
 				cmd = 0;
 			break;
@@ -12119,7 +12163,7 @@ static int vm_execmain(struct ast_channel *chan, const char *data)
  			break;
 		default:	/* Nothing */
 			ast_test_suite_event_notify("PLAYBACK", "Message: instructions");
-			cmd = vm_instructions(chan, vmu, &vms, 0, in_urgent);
+			cmd = vm_instructions(chan, vmu, &vms, 0, in_urgent, nodelete);
 			break;
 		}
 	}
diff --git a/apps/app_waitforcond.c b/apps/app_waitforcond.c
index 725afc099a7699110ffcb4663a00dac286d67574..d20ebdb24a90bfd52306e12f9d637d47c662f637 100644
--- a/apps/app_waitforcond.c
+++ b/apps/app_waitforcond.c
@@ -39,6 +39,11 @@
 
 /*** DOCUMENTATION
 	<application name="WaitForCondition" language="en_US">
+		<since>
+			<version>16.20.0</version>
+			<version>18.6.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Wait (sleep) until the given condition is true.
 		</synopsis>
diff --git a/apps/confbridge/conf_config_parser.c b/apps/confbridge/conf_config_parser.c
index 4f58c18e67f83e81ed16137084f02161f689027e..aceb3563fe8976f7ed9d97dc247a50c6035ce338 100644
--- a/apps/confbridge/conf_config_parser.c
+++ b/apps/confbridge/conf_config_parser.c
@@ -97,6 +97,9 @@
 				<configOption name="quiet">
 					<synopsis>Silence enter/leave prompts and user intros for this user</synopsis>
 				</configOption>
+				<configOption name="hear_own_join_sound">
+					<synopsis>Determines if the user also hears the join sound when they enter a conference</synopsis>
+				</configOption>
 				<configOption name="announce_user_count">
 					<synopsis>Sets if the number of users should be announced to the user</synopsis>
 				</configOption>
@@ -117,6 +120,9 @@
 				<configOption name="end_marked">
 					<synopsis>Kick the user from the conference when the last marked user leaves</synopsis>
 				</configOption>
+				<configOption name="end_marked_any">
+					<synopsis>Kick the user from the conference when any marked user leaves</synopsis>
+				</configOption>
 				<configOption name="talk_detection_events">
 					<synopsis>Set whether or not notifications of when a user begins and ends talking should be sent out as events over AMI</synopsis>
 				</configOption>
@@ -1437,10 +1443,7 @@ static int add_menu_entry(struct conf_menu *menu, const char *dtmf, const char *
 
 	/* if adding any of the actions failed, bail */
 	if (res) {
-		struct conf_menu_action *menu_action;
-		while ((menu_action = AST_LIST_REMOVE_HEAD(&menu_entry->actions, action))) {
-			ast_free(menu_action);
-		}
+		conf_menu_entry_destroy(menu_entry);
 		ast_free(menu_entry);
 		return -1;
 	}
@@ -1449,6 +1452,7 @@ static int add_menu_entry(struct conf_menu *menu, const char *dtmf, const char *
 	AST_LIST_TRAVERSE_SAFE_BEGIN(&menu->entries, cur, entry) {
 		if (!strcasecmp(cur->dtmf, menu_entry->dtmf)) {
 			AST_LIST_REMOVE_CURRENT(entry);
+			conf_menu_entry_destroy(cur);
 			ast_free(cur);
 			break;
 		}
@@ -1574,12 +1578,18 @@ static char *handle_cli_confbridge_show_user_profile(struct ast_cli_entry *e, in
 	ast_cli(a->fd,"Quiet:                   %s\n",
 		u_profile.flags & USER_OPT_QUIET ?
 		"enabled" : "disabled");
+	ast_cli(a->fd,"Hear Join:               %s\n",
+		u_profile.flags & USER_OPT_HEAR_OWN_JOIN_SOUND ?
+		"enabled" : "disabled");
 	ast_cli(a->fd,"Wait Marked:             %s\n",
 		u_profile.flags & USER_OPT_WAITMARKED ?
 		"enabled" : "disabled");
-	ast_cli(a->fd,"END Marked:              %s\n",
+	ast_cli(a->fd,"END Marked (All):        %s\n",
 		u_profile.flags & USER_OPT_ENDMARKED ?
 		"enabled" : "disabled");
+	ast_cli(a->fd,"END Marked (Any):        %s\n",
+		u_profile.flags & USER_OPT_ENDMARKEDANY ?
+		"enabled" : "disabled");
 	ast_cli(a->fd,"Drop_silence:            %s\n",
 		u_profile.flags & USER_OPT_DROP_SILENCE ?
 		"enabled" : "disabled");
@@ -2396,12 +2406,14 @@ int conf_load_config(void)
 	aco_option_register(&cfg_info, "startmuted", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_STARTMUTED);
 	aco_option_register(&cfg_info, "music_on_hold_when_empty", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_MUSICONHOLD);
 	aco_option_register(&cfg_info, "quiet", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_QUIET);
+	aco_option_register(&cfg_info, "hear_own_join_sound", ACO_EXACT, user_types, "yes", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_HEAR_OWN_JOIN_SOUND);
 	aco_option_register_custom(&cfg_info, "announce_user_count_all", ACO_EXACT, user_types, "no", announce_user_count_all_handler, 0);
 	aco_option_register(&cfg_info, "announce_user_count", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_ANNOUNCEUSERCOUNT);
 	/* Negative logic. Defaults to "yes" and evaluates with ast_false(). If !ast_false(), USER_OPT_NOONLYPERSON is cleared */
 	aco_option_register(&cfg_info, "announce_only_user", ACO_EXACT, user_types, "yes", OPT_BOOLFLAG_T, 0, FLDSET(struct user_profile, flags), USER_OPT_NOONLYPERSON);
 	aco_option_register(&cfg_info, "wait_marked", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_WAITMARKED);
 	aco_option_register(&cfg_info, "end_marked", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_ENDMARKED);
+	aco_option_register(&cfg_info, "end_marked_any", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_ENDMARKEDANY);
 	aco_option_register(&cfg_info, "talk_detection_events", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_TALKER_DETECT);
 	aco_option_register(&cfg_info, "dtmf_passthrough", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_DTMF_PASS);
 	aco_option_register(&cfg_info, "announce_join_leave", ACO_EXACT, user_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct user_profile, flags), USER_OPT_ANNOUNCE_JOIN_LEAVE);
diff --git a/apps/confbridge/conf_state.c b/apps/confbridge/conf_state.c
index 0c05a4caf27ab9223a54cef5932d38e2cc1aaf27..3b533d76ec248e50fd560b06cd1313f7f8beff24 100644
--- a/apps/confbridge/conf_state.c
+++ b/apps/confbridge/conf_state.c
@@ -14,9 +14,6 @@
  * This program is free software, distributed under the terms of
  * the GNU General Public License Version 2. See the LICENSE file
  * at the top of the source tree.
- *
- * Please follow coding guidelines
- * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
  */
 
 /*! \file
diff --git a/apps/confbridge/conf_state_empty.c b/apps/confbridge/conf_state_empty.c
index d2f4c8d109e52266198c3ade0a982abb2a65f093..2131f52761a018b4855ee0f7098fde85f0e67e4a 100644
--- a/apps/confbridge/conf_state_empty.c
+++ b/apps/confbridge/conf_state_empty.c
@@ -14,9 +14,6 @@
  * This program is free software, distributed under the terms of
  * the GNU General Public License Version 2. See the LICENSE file
  * at the top of the source tree.
- *
- * Please follow coding guidelines
- * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
  */
 
 /*! \file
diff --git a/apps/confbridge/conf_state_inactive.c b/apps/confbridge/conf_state_inactive.c
index b1a4a2ee2a0e2bf1ac8d001ad19a5b1e32e07e68..2d576c166f566225fb29b6bdea755c070aa188a2 100644
--- a/apps/confbridge/conf_state_inactive.c
+++ b/apps/confbridge/conf_state_inactive.c
@@ -14,9 +14,6 @@
  * This program is free software, distributed under the terms of
  * the GNU General Public License Version 2. See the LICENSE file
  * at the top of the source tree.
- *
- * Please follow coding guidelines
- * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
  */
 
 /*! \file
diff --git a/apps/confbridge/conf_state_multi.c b/apps/confbridge/conf_state_multi.c
index eca8899faa51d4c0981cd2bed5d3b63d2fe7864b..62d40c4ca88c0e2d5d90c7b37ff78c785dd8b357 100644
--- a/apps/confbridge/conf_state_multi.c
+++ b/apps/confbridge/conf_state_multi.c
@@ -14,9 +14,6 @@
  * This program is free software, distributed under the terms of
  * the GNU General Public License Version 2. See the LICENSE file
  * at the top of the source tree.
- *
- * Please follow coding guidelines
- * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
  */
 
 /*! \file
diff --git a/apps/confbridge/conf_state_multi_marked.c b/apps/confbridge/conf_state_multi_marked.c
index 17ca65cc21dfe7fec35d53a8e9c73405ee2f0047..ef3c4b33023a30f1242918e3de035aef30360a6f 100644
--- a/apps/confbridge/conf_state_multi_marked.c
+++ b/apps/confbridge/conf_state_multi_marked.c
@@ -14,9 +14,6 @@
  * This program is free software, distributed under the terms of
  * the GNU General Public License Version 2. See the LICENSE file
  * at the top of the source tree.
- *
- * Please follow coding guidelines
- * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
  */
 
 /*! \file
@@ -85,37 +82,39 @@ static void leave_marked(struct confbridge_user *user)
 
 	conf_remove_user_marked(user->conference, user);
 
-	if (user->conference->markedusers == 0) {
-		AST_LIST_TRAVERSE_SAFE_BEGIN(&user->conference->active_list, user_iter, list) {
-			/* Kick ENDMARKED cbu_iters */
-			if (ast_test_flag(&user_iter->u_profile, USER_OPT_ENDMARKED) && !user_iter->kicked) {
-				if (ast_test_flag(&user_iter->u_profile, USER_OPT_WAITMARKED)
-					&& !ast_test_flag(&user_iter->u_profile, USER_OPT_MARKEDUSER)) {
-					AST_LIST_REMOVE_CURRENT(list);
-					user_iter->conference->activeusers--;
-					AST_LIST_INSERT_TAIL(&user_iter->conference->waiting_list, user_iter, list);
-					user_iter->conference->waitingusers++;
-				}
-				user_iter->kicked = 1;
-				pbx_builtin_setvar_helper(user_iter->chan, "CONFBRIDGE_RESULT", "ENDMARKED");
-				ast_bridge_remove(user_iter->conference->bridge, user_iter->chan);
-			} else if (ast_test_flag(&user_iter->u_profile, USER_OPT_WAITMARKED)
-				&& !ast_test_flag(&user_iter->u_profile, USER_OPT_MARKEDUSER)) {
-				need_prompt = 1;
-
+	/* If all marked users have left, or we're set to kick if any marked user leaves, then boot everyone */
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&user->conference->active_list, user_iter, list) {
+		if (user->conference->markedusers > 0 && !ast_test_flag(&user_iter->u_profile, USER_OPT_ENDMARKEDANY)) {
+			continue;
+		}
+		/* Kick ENDMARKED cbu_iters */
+		if ((ast_test_flag(&user_iter->u_profile, USER_OPT_ENDMARKED) || ast_test_flag(&user_iter->u_profile, USER_OPT_ENDMARKEDANY)) && !user_iter->kicked) {
+			if (ast_test_flag(&user_iter->u_profile, USER_OPT_WAITMARKED)
+				&& (!ast_test_flag(&user_iter->u_profile, USER_OPT_MARKEDUSER) || ast_test_flag(&user_iter->u_profile, USER_OPT_ENDMARKEDANY))) {
 				AST_LIST_REMOVE_CURRENT(list);
 				user_iter->conference->activeusers--;
 				AST_LIST_INSERT_TAIL(&user_iter->conference->waiting_list, user_iter, list);
 				user_iter->conference->waitingusers++;
-			} else {
-				/* User is neither wait_marked nor end_marked; however, they
-				 * should still hear the prompt.
-				 */
-				need_prompt = 1;
 			}
+			user_iter->kicked = 1;
+			pbx_builtin_setvar_helper(user_iter->chan, "CONFBRIDGE_RESULT", "ENDMARKED");
+			ast_bridge_remove(user_iter->conference->bridge, user_iter->chan);
+		} else if (ast_test_flag(&user_iter->u_profile, USER_OPT_WAITMARKED)
+			&& !ast_test_flag(&user_iter->u_profile, USER_OPT_MARKEDUSER)) {
+			need_prompt = 1;
+
+			AST_LIST_REMOVE_CURRENT(list);
+			user_iter->conference->activeusers--;
+			AST_LIST_INSERT_TAIL(&user_iter->conference->waiting_list, user_iter, list);
+			user_iter->conference->waitingusers++;
+		} else {
+			/* User is neither wait_marked nor end_marked nor end_marked_any; however, they
+			 * should still hear the prompt.
+			 */
+			need_prompt = 1;
 		}
-		AST_LIST_TRAVERSE_SAFE_END;
 	}
+	AST_LIST_TRAVERSE_SAFE_END;
 
 	switch (user->conference->activeusers) {
 	case 0:
diff --git a/apps/confbridge/conf_state_single.c b/apps/confbridge/conf_state_single.c
index b3881fa3dffe3af95924eb293d36d1dfbe204deb..188e4f3553efc03bbb0a759fcb61cce27fd63bee 100644
--- a/apps/confbridge/conf_state_single.c
+++ b/apps/confbridge/conf_state_single.c
@@ -14,9 +14,6 @@
  * This program is free software, distributed under the terms of
  * the GNU General Public License Version 2. See the LICENSE file
  * at the top of the source tree.
- *
- * Please follow coding guidelines
- * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
  */
 
 /*! \file
diff --git a/apps/confbridge/conf_state_single_marked.c b/apps/confbridge/conf_state_single_marked.c
index c13ef1636963608524be7f8ce19b1d67e773c30a..79501c662ff7ceb62c7c99fd9ed1da85a508389b 100644
--- a/apps/confbridge/conf_state_single_marked.c
+++ b/apps/confbridge/conf_state_single_marked.c
@@ -14,9 +14,6 @@
  * This program is free software, distributed under the terms of
  * the GNU General Public License Version 2. See the LICENSE file
  * at the top of the source tree.
- *
- * Please follow coding guidelines
- * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
  */
 
 /*! \file
diff --git a/apps/confbridge/include/conf_state.h b/apps/confbridge/include/conf_state.h
index b6f6f473d52fe3d29b12380ae97818ededb277e3..a9760c901244e351845247e1f35c22c8a529052b 100644
--- a/apps/confbridge/include/conf_state.h
+++ b/apps/confbridge/include/conf_state.h
@@ -14,9 +14,6 @@
  * This program is free software, distributed under the terms of
  * the GNU General Public License Version 2. See the LICENSE file
  * at the top of the source tree.
- *
- * Please follow coding guidelines
- * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
  */
 
 /*! \file
diff --git a/apps/confbridge/include/confbridge.h b/apps/confbridge/include/confbridge.h
index 95a07321fb63c0ae5246dd6d4c56aad49dfb33f5..a6fdbb4b2345295c161b141628ce21404c3ac5eb 100644
--- a/apps/confbridge/include/confbridge.h
+++ b/apps/confbridge/include/confbridge.h
@@ -70,6 +70,8 @@ enum user_profile_flags {
 	USER_OPT_ECHO_EVENTS = (1 << 18), /*!< Send events only to the admin(s) */
 	USER_OPT_TEXT_MESSAGING = (1 << 19), /*!< Send text messages to the user */
 	USER_OPT_ANSWER_CHANNEL = (1 << 20), /*!< Sets if the channel should be answered if currently unanswered */
+	USER_OPT_HEAR_OWN_JOIN_SOUND  = (1 << 21), /*!< Set if the caller should hear the join sound */
+	USER_OPT_ENDMARKEDANY = (1 << 22), /*!< Set if the user should be kicked after any marked user exits */
 };
 
 enum bridge_profile_flags {
diff --git a/asterisk-18.11.2-summary.html b/asterisk-18.11.2-summary.html
deleted file mode 100644
index ea4805b18d885980d67e3238208de05a59aa342e..0000000000000000000000000000000000000000
--- a/asterisk-18.11.2-summary.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><title>Release Summary - asterisk-18.11.2</title><h1 align="center"><a name="top">Release Summary</a></h1><h3 align="center">asterisk-18.11.2</h3><h3 align="center">Date: 2022-04-14</h3><h3 align="center">&lt;asteriskteam@digium.com&gt;</h3><hr><h2 align="center">Table of Contents</h2><ol>
-<li><a href="#summary">Summary</a></li>
-<li><a href="#contributors">Contributors</a></li>
-<li><a href="#closed_issues">Closed Issues</a></li>
-<li><a href="#commits">Other Changes</a></li>
-<li><a href="#diffstat">Diffstat</a></li>
-</ol><hr><a name="summary"><h2 align="center">Summary</h2></a><center><a href="#top">[Back to Top]</a></center><p>This release has been made to address one or more security vulnerabilities that have been identified. A security advisory document has been published for each vulnerability that includes additional information. Users of versions of Asterisk that are affected are strongly encouraged to review the advisories and determine what action they should take to protect their systems from these issues.</p><p>Security Advisories:</p><ul>
-<li><a href="http://downloads.asterisk.org/pub/security/AST-2022-001.html">AST-2022-001</a></li>
-<li><a href="http://downloads.asterisk.org/pub/security/AST-2022-002.html">AST-2022-002</a></li>
-<li><a href="http://downloads.asterisk.org/pub/security/AST-2022-003.html">AST-2022-003</a></li>
-</ul><p>The data in this summary reflects changes that have been made since the previous release, asterisk-18.11.1.</p><hr><a name="contributors"><h2 align="center">Contributors</h2></a><center><a href="#top">[Back to Top]</a></center><p>This table lists the people who have submitted code, those that have tested patches, as well as those that reported issues on the issue tracker that were resolved in this release. For coders, the number is how many of their patches (of any size) were committed into this release. For testers, the number is the number of times their name was listed as assisting with testing a patch. Finally, for reporters, the number is the number of issues that they reported that were affected by commits that went into this release.</p><table width="100%" border="0">
-<tr><th width="33%">Coders</th><th width="33%">Testers</th><th width="33%">Reporters</th></tr>
-<tr valign="top"><td width="33%">3 Asterisk Development Team <asteriskteam@digium.com><br/>2 Ben Ford <bford@digium.com><br/>1 Joshua C. Colp <jcolp@sangoma.com><br/></td><td width="33%"><td width="33%">1 Benjamin Keith Ford <bford@digium.com><br/>1 Clint Ruoho <clint@ruoho.org><br/>1 Leandro Dardini <ldardini@gmail.com><br/></td></tr>
-</table><hr><a name="closed_issues"><h2 align="center">Closed Issues</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all issues from the issue tracker that were closed by changes that went into this release.</p><h3>Security</h3><h4>Category: Functions/func_odbc</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29838">ASTERISK-29838</a>: ${SQL_ESC()} not correctly escaping a terminating \<br/>Reported by: Leandro Dardini<ul>
-<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=88522c22aa1985667c813c11df6f53d9f78b80a1">[88522c22aa]</a> Joshua C. Colp -- func_odbc: Add SQL_ESC_BACKSLASHES dialplan function.</li>
-</ul><br><h4>Category: Resources/res_stir_shaken</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29476">ASTERISK-29476</a>: res_stir_shaken: Blind SSRF vulnerabilities<br/>Reported by: Clint Ruoho<ul>
-<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=353142a2b4a8f30b860754115feae4618f48cfcb">[353142a2b4]</a> Ben Ford -- AST-2022-002 - res_stir_shaken/curl: Add ACL checks for Identity header.</li>
-</ul><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29872">ASTERISK-29872</a>: res_stir_shaken: Resource exhaustion with large files<br/>Reported by: Benjamin Keith Ford<ul>
-<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=1fdb1a6edf997ed1337df20228748199f12b039b">[1fdb1a6edf]</a> Ben Ford -- AST-2022-001 - res_stir_shaken/curl: Limit file size and check start.</li>
-</ul><br><hr><a name="commits"><h2 align="center">Commits Not Associated with an Issue</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all changes that went into this release that did not reference a JIRA issue.</p><table width="100%" border="1">
-<tr><th>Revision</th><th>Author</th><th>Summary</th></tr>
-<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=94eac3c13cdef5aa41848b6674f8bcd1f59d54b8">94eac3c13c</a></td><td>Asterisk Development Team</td><td>Doing a fresh summary</td></tr>
-<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=3ca29c95549c761790610b94411e38f2193af4c6">3ca29c9554</a></td><td>Asterisk Development Team</td><td>Update for 18.11.2</td></tr>
-<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=1422d098e70b09193f1d85c87aabdf86bf6ac059">1422d098e7</a></td><td>Asterisk Development Team</td><td>Update CHANGES and UPGRADE.txt for 18.11.2</td></tr>
-</table><hr><a name="diffstat"><h2 align="center">Diffstat Results</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a summary of the changes to the source code that went into this release that was generated using the diffstat utility.</p><pre>asterisk-18.11.1-summary.html             |   16 -
-asterisk-18.11.1-summary.txt              |   91 -----------
-b/.version                                |    2
-b/CHANGES                                 |   12 +
-b/ChangeLog                               |   57 +++++++
-b/configs/samples/func_odbc.conf.sample   |    4
-b/configs/samples/pjsip.conf.sample       |    4
-b/configs/samples/stir_shaken.conf.sample |   18 ++
-b/funcs/func_odbc.c                       |   39 ++++
-b/include/asterisk/res_pjsip.h            |    2
-b/include/asterisk/res_stir_shaken.h      |   54 ++++++
-b/res/res_pjsip/pjsip_config.xml          |    7
-b/res/res_pjsip/pjsip_configuration.c     |    1
-b/res/res_pjsip_stir_shaken.c             |   14 +
-b/res/res_stir_shaken.c                   |   90 +++++++++--
-b/res/res_stir_shaken/curl.c              |  177 +++++++++++++++++-----
-b/res/res_stir_shaken/curl.h              |    5
-b/res/res_stir_shaken/profile.c           |  241 ++++++++++++++++++++++++++++++
-b/res/res_stir_shaken/profile.h           |   39 ++++
-b/res/res_stir_shaken/profile_private.h   |   40 ++++
-b/res/res_stir_shaken/stir_shaken.c       |   20 --
-21 files changed, 760 insertions(+), 173 deletions(-)</pre><br></html>
\ No newline at end of file
diff --git a/asterisk-18.11.2-summary.txt b/asterisk-18.11.2-summary.txt
deleted file mode 100644
index 807c1bcbf57fd96456ab69f83dc8e4882042d48f..0000000000000000000000000000000000000000
--- a/asterisk-18.11.2-summary.txt
+++ /dev/null
@@ -1,140 +0,0 @@
-                                Release Summary
-
-                                asterisk-18.11.2
-
-                                Date: 2022-04-14
-
-                           <asteriskteam@digium.com>
-
-     ----------------------------------------------------------------------
-
-                               Table of Contents
-
-    1. Summary
-    2. Contributors
-    3. Closed Issues
-    4. Other Changes
-    5. Diffstat
-
-     ----------------------------------------------------------------------
-
-                                    Summary
-
-                                 [Back to Top]
-
-   This release has been made to address one or more security vulnerabilities
-   that have been identified. A security advisory document has been published
-   for each vulnerability that includes additional information. Users of
-   versions of Asterisk that are affected are strongly encouraged to review
-   the advisories and determine what action they should take to protect their
-   systems from these issues.
-
-   Security Advisories:
-
-     * AST-2022-001
-     * AST-2022-002
-     * AST-2022-003
-
-   The data in this summary reflects changes that have been made since the
-   previous release, asterisk-18.11.1.
-
-     ----------------------------------------------------------------------
-
-                                  Contributors
-
-                                 [Back to Top]
-
-   This table lists the people who have submitted code, those that have
-   tested patches, as well as those that reported issues on the issue tracker
-   that were resolved in this release. For coders, the number is how many of
-   their patches (of any size) were committed into this release. For testers,
-   the number is the number of times their name was listed as assisting with
-   testing a patch. Finally, for reporters, the number is the number of
-   issues that they reported that were affected by commits that went into
-   this release.
-
-   Coders                      Testers               Reporters                
-   3 Asterisk Development Team                       1 Benjamin Keith Ford    
-   2 Ben Ford                                        1 Clint Ruoho            
-   1 Joshua C. Colp                                  1 Leandro Dardini        
-
-     ----------------------------------------------------------------------
-
-                                 Closed Issues
-
-                                 [Back to Top]
-
-   This is a list of all issues from the issue tracker that were closed by
-   changes that went into this release.
-
-  Security
-
-    Category: Functions/func_odbc
-
-   ASTERISK-29838: ${SQL_ESC()} not correctly escaping a terminating \
-   Reported by: Leandro Dardini
-     * [88522c22aa] Joshua C. Colp -- func_odbc: Add SQL_ESC_BACKSLASHES
-       dialplan function.
-
-    Category: Resources/res_stir_shaken
-
-   ASTERISK-29476: res_stir_shaken: Blind SSRF vulnerabilities
-   Reported by: Clint Ruoho
-     * [353142a2b4] Ben Ford -- AST-2022-002 - res_stir_shaken/curl: Add ACL
-       checks for Identity header.
-   ASTERISK-29872: res_stir_shaken: Resource exhaustion with large files
-   Reported by: Benjamin Keith Ford
-     * [1fdb1a6edf] Ben Ford -- AST-2022-001 - res_stir_shaken/curl: Limit
-       file size and check start.
-
-     ----------------------------------------------------------------------
-
-                      Commits Not Associated with an Issue
-
-                                 [Back to Top]
-
-   This is a list of all changes that went into this release that did not
-   reference a JIRA issue.
-
-   +------------------------------------------------------------------------+
-   | Revision   | Author                    | Summary                       |
-   |------------+---------------------------+-------------------------------|
-   | 94eac3c13c | Asterisk Development Team | Doing a fresh summary         |
-   |------------+---------------------------+-------------------------------|
-   | 3ca29c9554 | Asterisk Development Team | Update for 18.11.2            |
-   |------------+---------------------------+-------------------------------|
-   | 1422d098e7 | Asterisk Development Team | Update CHANGES and            |
-   |            |                           | UPGRADE.txt for 18.11.2       |
-   +------------------------------------------------------------------------+
-
-     ----------------------------------------------------------------------
-
-                                Diffstat Results
-
-                                 [Back to Top]
-
-   This is a summary of the changes to the source code that went into this
-   release that was generated using the diffstat utility.
-
- asterisk-18.11.1-summary.html             |   16 -
- asterisk-18.11.1-summary.txt              |   91 -----------
- b/.version                                |    2
- b/CHANGES                                 |   12 +
- b/ChangeLog                               |   57 +++++++
- b/configs/samples/func_odbc.conf.sample   |    4
- b/configs/samples/pjsip.conf.sample       |    4
- b/configs/samples/stir_shaken.conf.sample |   18 ++
- b/funcs/func_odbc.c                       |   39 ++++
- b/include/asterisk/res_pjsip.h            |    2
- b/include/asterisk/res_stir_shaken.h      |   54 ++++++
- b/res/res_pjsip/pjsip_config.xml          |    7
- b/res/res_pjsip/pjsip_configuration.c     |    1
- b/res/res_pjsip_stir_shaken.c             |   14 +
- b/res/res_stir_shaken.c                   |   90 +++++++++--
- b/res/res_stir_shaken/curl.c              |  177 +++++++++++++++++-----
- b/res/res_stir_shaken/curl.h              |    5
- b/res/res_stir_shaken/profile.c           |  241 ++++++++++++++++++++++++++++++
- b/res/res_stir_shaken/profile.h           |   39 ++++
- b/res/res_stir_shaken/profile_private.h   |   40 ++++
- b/res/res_stir_shaken/stir_shaken.c       |   20 --
- 21 files changed, 760 insertions(+), 173 deletions(-)
diff --git a/asterisk-18.5.1-summary.html b/asterisk-18.5.1-summary.html
deleted file mode 100644
index 9a7d3d064720050a82ac028a329fbedbb0872f8f..0000000000000000000000000000000000000000
--- a/asterisk-18.5.1-summary.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><title>Release Summary - asterisk-18.5.1</title><h1 align="center"><a name="top">Release Summary</a></h1><h3 align="center">asterisk-18.5.1</h3><h3 align="center">Date: 2021-07-22</h3><h3 align="center">&lt;asteriskteam@digium.com&gt;</h3><hr><h2 align="center">Table of Contents</h2><ol>
-<li><a href="#summary">Summary</a></li>
-<li><a href="#contributors">Contributors</a></li>
-<li><a href="#closed_issues">Closed Issues</a></li>
-<li><a href="#diffstat">Diffstat</a></li>
-</ol><hr><a name="summary"><h2 align="center">Summary</h2></a><center><a href="#top">[Back to Top]</a></center><p>This release has been made to address one or more security vulnerabilities that have been identified. A security advisory document has been published for each vulnerability that includes additional information. Users of versions of Asterisk that are affected are strongly encouraged to review the advisories and determine what action they should take to protect their systems from these issues.</p><p>Security Advisories:</p><ul>
-<li><a href="http://downloads.asterisk.org/pub/security/AST-2021-007,AST-2021-008,AST-2021-009.html">AST-2021-007,AST-2021-008,AST-2021-009</a></li>
-</ul><p>The data in this summary reflects changes that have been made since the previous release, asterisk-18.5.0.</p><hr><a name="contributors"><h2 align="center">Contributors</h2></a><center><a href="#top">[Back to Top]</a></center><p>This table lists the people who have submitted code, those that have tested patches, as well as those that reported issues on the issue tracker that were resolved in this release. For coders, the number is how many of their patches (of any size) were committed into this release. For testers, the number is the number of times their name was listed as assisting with testing a patch. Finally, for reporters, the number is the number of issues that they reported that were affected by commits that went into this release.</p><table width="100%" border="0">
-<tr><th width="33%">Coders</th><th width="33%">Testers</th><th width="33%">Reporters</th></tr>
-<tr valign="top"><td width="33%">2 Kevin Harwell <kharwell@sangoma.com><br/>1 Joshua C. Colp <jcolp@sangoma.com><br/></td><td width="33%"><td width="33%">1 Michael Welk <dl5ocd@darc.de><br/>1 Ivan Poddubny <ivan.poddubny@gmail.com><br/>1 Andrew Yager <andrew@rwts.com.au><br/></td></tr>
-</table><hr><a name="closed_issues"><h2 align="center">Closed Issues</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all issues from the issue tracker that were closed by changes that went into this release.</p><h3>Security</h3><h4>Category: Channels/chan_pjsip</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29415">ASTERISK-29415</a>: Crash in PJSIP TLS transport <br/>Reported by: Andrew Yager<ul>
-<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=6c1aec36cb55ca1f85452dbe54e6ed4a3421a6b2">[6c1aec36cb]</a> Kevin Harwell -- AST-2021-009 - pjproject-bundled: Avoid crash during handshake for TLS</li>
-</ul><br><h4>Category: Resources/res_pjsip_session</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29381">ASTERISK-29381</a>: chan_pjsip: Remote denial of service by an authenticated user<br/>Reported by: Ivan Poddubny<ul>
-<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=4a525a89719ede3f8719d2cf2aeca410467e8df8">[4a525a8971]</a> Joshua C. Colp -- AST-2021-007 - res_pjsip_session: Don't offer if no channel exists.</li>
-</ul><br><h3>Bug</h3><h4>Category: Channels/chan_iax2</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29392">ASTERISK-29392</a>: chan_iax2: Asterisk crashes when queueing video with format<br/>Reported by: Michael Welk<ul>
-<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=98e0b536d73959f8868a8e7435bec1b425e515ac">[98e0b536d7]</a> Kevin Harwell -- AST-2021-008 - chan_iax2: remote crash on unsupported media format</li>
-</ul><br><hr><a name="diffstat"><h2 align="center">Diffstat Results</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a summary of the changes to the source code that went into this release that was generated using the diffstat utility.</p><pre>channels/chan_iax2.c                                                   |   40 +-
-res/res_pjsip_session.c                                                |   10
-third-party/pjproject/patches/0110-tls-parent-listener-destroyed.patch |  166 ++++++++++
-third-party/pjproject/patches/0111-ssl-premature-destroy.patch         |  136 ++++++++
-4 files changed, 343 insertions(+), 9 deletions(-)</pre><br></html>
\ No newline at end of file
diff --git a/asterisk-18.5.1-summary.txt b/asterisk-18.5.1-summary.txt
deleted file mode 100644
index c4d0b7e842b7ce4816eb8bbfe534c03ef4734f07..0000000000000000000000000000000000000000
--- a/asterisk-18.5.1-summary.txt
+++ /dev/null
@@ -1,107 +0,0 @@
-                                Release Summary
-
-                                asterisk-18.5.1
-
-                                Date: 2021-07-22
-
-                           <asteriskteam@digium.com>
-
-     ----------------------------------------------------------------------
-
-                               Table of Contents
-
-    1. Summary
-    2. Contributors
-    3. Closed Issues
-    4. Diffstat
-
-     ----------------------------------------------------------------------
-
-                                    Summary
-
-                                 [Back to Top]
-
-   This release has been made to address one or more security vulnerabilities
-   that have been identified. A security advisory document has been published
-   for each vulnerability that includes additional information. Users of
-   versions of Asterisk that are affected are strongly encouraged to review
-   the advisories and determine what action they should take to protect their
-   systems from these issues.
-
-   Security Advisories:
-
-     * AST-2021-007,AST-2021-008,AST-2021-009
-
-   The data in this summary reflects changes that have been made since the
-   previous release, asterisk-18.5.0.
-
-     ----------------------------------------------------------------------
-
-                                  Contributors
-
-                                 [Back to Top]
-
-   This table lists the people who have submitted code, those that have
-   tested patches, as well as those that reported issues on the issue tracker
-   that were resolved in this release. For coders, the number is how many of
-   their patches (of any size) were committed into this release. For testers,
-   the number is the number of times their name was listed as assisting with
-   testing a patch. Finally, for reporters, the number is the number of
-   issues that they reported that were affected by commits that went into
-   this release.
-
-   Coders                   Testers                  Reporters                
-   2 Kevin Harwell                                   1 Michael Welk           
-   1 Joshua C. Colp                                  1 Ivan Poddubny          
-                                                     1 Andrew Yager           
-
-     ----------------------------------------------------------------------
-
-                                 Closed Issues
-
-                                 [Back to Top]
-
-   This is a list of all issues from the issue tracker that were closed by
-   changes that went into this release.
-
-  Security
-
-    Category: Channels/chan_pjsip
-
-   ASTERISK-29415: Crash in PJSIP TLS transport
-   Reported by: Andrew Yager
-     * [6c1aec36cb] Kevin Harwell -- AST-2021-009 - pjproject-bundled: Avoid
-       crash during handshake for TLS
-
-    Category: Resources/res_pjsip_session
-
-   ASTERISK-29381: chan_pjsip: Remote denial of service by an authenticated
-   user
-   Reported by: Ivan Poddubny
-     * [4a525a8971] Joshua C. Colp -- AST-2021-007 - res_pjsip_session: Don't
-       offer if no channel exists.
-
-  Bug
-
-    Category: Channels/chan_iax2
-
-   ASTERISK-29392: chan_iax2: Asterisk crashes when queueing video with
-   format
-   Reported by: Michael Welk
-     * [98e0b536d7] Kevin Harwell -- AST-2021-008 - chan_iax2: remote crash
-       on unsupported media format
-
-     ----------------------------------------------------------------------
-
-                                Diffstat Results
-
-                                 [Back to Top]
-
-   This is a summary of the changes to the source code that went into this
-   release that was generated using the diffstat utility.
-
- channels/chan_iax2.c                                                   |   40 +-
- res/res_pjsip_session.c                                                |   10
- third-party/pjproject/patches/0110-tls-parent-listener-destroyed.patch |  166 ++++++++++
- third-party/pjproject/patches/0111-ssl-premature-destroy.patch         |  136 ++++++++
- 4 files changed, 343 insertions(+), 9 deletions(-)
diff --git a/autoconf/ast_pkgconfig.m4 b/autoconf/ast_pkgconfig.m4
index 3415ed5479380da99e42fde19e4123f1da6d864c..bb9c60c93913be2d82328c0da4afc81bab775c11 100644
--- a/autoconf/ast_pkgconfig.m4
+++ b/autoconf/ast_pkgconfig.m4
@@ -2,6 +2,7 @@
 # AST_PKG_CONFIG_CHECK([package], [component])
 AC_DEFUN([AST_PKG_CONFIG_CHECK],
 [
+   AC_REQUIRE([AST_PROG_SED])dnl
    if test "x${PBX_$1}" != "x1" -a "${USE_$1}" != "no"; then
       PKG_CHECK_MODULES($1, $2, [
             PBX_$1=1
diff --git a/bridges/bridge_builtin_features.c b/bridges/bridge_builtin_features.c
index 671cfb9385b764bb2fb571d8bc9eb44085245b2d..9f8143e455bb390b4eeb81ef3420bfec68242a6a 100644
--- a/bridges/bridge_builtin_features.c
+++ b/bridges/bridge_builtin_features.c
@@ -53,6 +53,7 @@
 #include "asterisk/mixmonitor.h"
 #include "asterisk/audiohook.h"
 #include "asterisk/causes.h"
+#include "asterisk/beep.h"
 
 enum set_touch_variables_res {
 	SET_TOUCH_SUCCESS,
@@ -78,12 +79,13 @@ static void set_touch_variable(enum set_touch_variables_res *res, struct ast_cha
 	}
 }
 
-static enum set_touch_variables_res set_touch_variables(struct ast_channel *chan, int is_mixmonitor, char **touch_format, char **touch_monitor, char **touch_monitor_prefix)
+static enum set_touch_variables_res set_touch_variables(struct ast_channel *chan, int is_mixmonitor, char **touch_format, char **touch_monitor, char **touch_monitor_prefix, char **touch_monitor_beep)
 {
 	enum set_touch_variables_res res = SET_TOUCH_UNSET;
 	const char *var_format;
 	const char *var_monitor;
 	const char *var_prefix;
+	const char *var_beep;
 
 	SCOPED_CHANNELLOCK(lock, chan);
 
@@ -91,14 +93,17 @@ static enum set_touch_variables_res set_touch_variables(struct ast_channel *chan
 		var_format = "TOUCH_MIXMONITOR_FORMAT";
 		var_monitor = "TOUCH_MIXMONITOR";
 		var_prefix = "TOUCH_MIXMONITOR_PREFIX";
+		var_beep = "TOUCH_MIXMONITOR_BEEP";
 	} else {
 		var_format = "TOUCH_MONITOR_FORMAT";
 		var_monitor = "TOUCH_MONITOR";
 		var_prefix = "TOUCH_MONITOR_PREFIX";
+		var_beep = "TOUCH_MONITOR_BEEP";
 	}
 	set_touch_variable(&res, chan, var_format, touch_format);
 	set_touch_variable(&res, chan, var_monitor, touch_monitor);
 	set_touch_variable(&res, chan, var_prefix, touch_monitor_prefix);
+	set_touch_variable(&res, chan, var_beep, touch_monitor_beep);
 
 	return res;
 }
@@ -141,20 +146,22 @@ static void start_automonitor(struct ast_bridge_channel *bridge_channel, struct
 	char *touch_filename;
 	size_t len;
 	int x;
+	char beep_id[64] = "";
 	enum set_touch_variables_res set_touch_res;
 
 	RAII_VAR(char *, touch_format, NULL, ast_free);
 	RAII_VAR(char *, touch_monitor, NULL, ast_free);
 	RAII_VAR(char *, touch_monitor_prefix, NULL, ast_free);
+	RAII_VAR(char *, touch_monitor_beep, NULL, ast_free);
 
 	set_touch_res = set_touch_variables(bridge_channel->chan, 0, &touch_format,
-		&touch_monitor, &touch_monitor_prefix);
+		&touch_monitor, &touch_monitor_prefix, &touch_monitor_beep);
 	switch (set_touch_res) {
 	case SET_TOUCH_SUCCESS:
 		break;
 	case SET_TOUCH_UNSET:
 		set_touch_res = set_touch_variables(peer_chan, 0, &touch_format, &touch_monitor,
-			&touch_monitor_prefix);
+			&touch_monitor_prefix, &touch_monitor_beep);
 		if (set_touch_res == SET_TOUCH_ALLOC_FAILURE) {
 			return;
 		}
@@ -195,7 +202,28 @@ static void start_automonitor(struct ast_bridge_channel *bridge_channel, struct
 
 	ast_verb(4, "AutoMonitor used to record call. Filename: %s\n", touch_filename);
 
-	if (ast_monitor_start(peer_chan, touch_format, touch_filename, 1, X_REC_IN | X_REC_OUT, NULL)) {
+	if (!ast_strlen_zero(touch_monitor_beep)) {
+		unsigned int interval = 15;
+		if (sscanf(touch_monitor_beep, "%30u", &interval) != 1) {
+			ast_log(LOG_WARNING, "Invalid interval '%s' for periodic beep. Using default of %u\n",
+						touch_monitor_beep, interval);
+		}
+
+		if (interval > 0) {
+			if (interval < 5) {
+				interval = 5;
+				ast_log(LOG_WARNING, "Interval '%s' too small for periodic beep. Using minimum of %u\n",
+						touch_monitor_beep, interval);
+			}
+
+			if (ast_beep_start(peer_chan, interval, beep_id, sizeof(beep_id))) {
+				ast_log(LOG_WARNING, "Unable to enable periodic beep, please ensure func_periodic_hook is loaded.\n");
+				return;
+			}
+		}
+	}
+
+	if (ast_monitor_start(peer_chan, touch_format, touch_filename, 1, X_REC_IN | X_REC_OUT, beep_id)) {
 		ast_verb(4, "AutoMonitor feature was tried by '%s' but monitor failed to start.\n",
 			ast_channel_name(bridge_channel->chan));
 		return;
@@ -322,7 +350,7 @@ static void stop_automixmonitor(struct ast_bridge_channel *bridge_channel, struc
 
 static void start_automixmonitor(struct ast_bridge_channel *bridge_channel, struct ast_channel *peer_chan, struct ast_features_general_config *features_cfg, const char *start_message)
 {
-	char *touch_filename;
+	char *touch_filename, mix_options[32] = "b";
 	size_t len;
 	int x;
 	enum set_touch_variables_res set_touch_res;
@@ -330,15 +358,16 @@ static void start_automixmonitor(struct ast_bridge_channel *bridge_channel, stru
 	RAII_VAR(char *, touch_format, NULL, ast_free);
 	RAII_VAR(char *, touch_monitor, NULL, ast_free);
 	RAII_VAR(char *, touch_monitor_prefix, NULL, ast_free);
+	RAII_VAR(char *, touch_monitor_beep, NULL, ast_free);
 
 	set_touch_res = set_touch_variables(bridge_channel->chan, 1, &touch_format,
-		&touch_monitor, &touch_monitor_prefix);
+		&touch_monitor, &touch_monitor_prefix, &touch_monitor_beep);
 	switch (set_touch_res) {
 	case SET_TOUCH_SUCCESS:
 		break;
 	case SET_TOUCH_UNSET:
 		set_touch_res = set_touch_variables(peer_chan, 1, &touch_format, &touch_monitor,
-			&touch_monitor_prefix);
+			&touch_monitor_prefix, &touch_monitor_beep);
 		if (set_touch_res == SET_TOUCH_ALLOC_FAILURE) {
 			return;
 		}
@@ -381,7 +410,22 @@ static void start_automixmonitor(struct ast_bridge_channel *bridge_channel, stru
 
 	ast_verb(4, "AutoMixMonitor used to record call. Filename: %s\n", touch_filename);
 
-	if (ast_start_mixmonitor(peer_chan, touch_filename, "b")) {
+	if (!ast_strlen_zero(touch_monitor_beep)) {
+		unsigned int interval = 15;
+		if (sscanf(touch_monitor_beep, "%30u", &interval) != 1) {
+			ast_log(LOG_WARNING, "Invalid interval '%s' for periodic beep. Using default of %u\n",
+						touch_monitor_beep, interval);
+		}
+
+		if (interval < 5) {
+			interval = 5;
+			ast_log(LOG_WARNING, "Interval '%s' too small for periodic beep. Using minimum of %u\n",
+					touch_monitor_beep, interval);
+		}
+		snprintf(mix_options, sizeof(mix_options), "bB(%d)", interval);
+	}
+
+	if (ast_start_mixmonitor(peer_chan, touch_filename, mix_options)) {
 		ast_verb(4, "AutoMixMonitor feature was tried by '%s' but MixMonitor failed to start.\n",
 			ast_channel_name(bridge_channel->chan));
 
diff --git a/bridges/bridge_simple.c b/bridges/bridge_simple.c
index 1e224f782b0d1c8ad0749b6af6c0a9f6f5d4877d..ba300ed4d3f9520c073b20c80062921cf33a0166 100644
--- a/bridges/bridge_simple.c
+++ b/bridges/bridge_simple.c
@@ -128,6 +128,7 @@ static int simple_bridge_join(struct ast_bridge *bridge, struct ast_bridge_chann
 	struct ast_stream_topology *new_top;
 	struct ast_channel *c0 = AST_LIST_FIRST(&bridge->channels)->chan;
 	struct ast_channel *c1 = AST_LIST_LAST(&bridge->channels)->chan;
+	int unhold_c0, unhold_c1;
 
 	/*
 	 * If this is the first channel we can't make it compatible...
@@ -152,9 +153,29 @@ static int simple_bridge_join(struct ast_bridge *bridge, struct ast_bridge_chann
 		SWAP(c0, c1);
 	}
 	new_top = simple_bridge_request_stream_topology_update(existing_top, req_top);
+
+	/* The ast_channel_hold_state() and ast_channel_name() accessors need to be
+	 * called with the associated channel lock held.
+	 */
+	if ((unhold_c1 = ast_channel_hold_state(c1) == AST_CONTROL_HOLD)) {
+		ast_debug(1, "Channel %s simulating UNHOLD for bridge simple join.\n", ast_channel_name(c1));
+	}
+
+	if ((unhold_c0 = ast_channel_hold_state(c0) == AST_CONTROL_HOLD)) {
+		ast_debug(1, "Channel %s simulating UNHOLD for bridge simple join.\n", ast_channel_name(c0));
+	}
+
 	ast_channel_unlock(c0);
 	ast_channel_unlock(c1);
 
+	if (unhold_c1) {
+		ast_indicate(c1, AST_CONTROL_UNHOLD);
+	}
+
+	if (unhold_c0) {
+		ast_indicate(c0, AST_CONTROL_UNHOLD);
+	}
+
 	if (!new_top) {
 		/* Failure.  We'll just have to live with the current topology. */
 		return 0;
diff --git a/build_tools/download_externals b/build_tools/download_externals
index 0f82eff6515c7699f1e9fb9aabea9740dac871f4..cb2cd0336ca23743b2398e4927665db9805f927c 100755
--- a/build_tools/download_externals
+++ b/build_tools/download_externals
@@ -109,7 +109,7 @@ if [[ -z "${remote_url}" ]] ; then
 		remote_url="${remote_url}/asterisk-${major_version}/x86-${host_bits}"
 	else
 		directory_name=$(${XMLSTARLET} sel -t -v "/menu/category/member[@name = '${member_name}']/member_data/downloader/@directory_name" ${ASTTOPDIR}/menuselect-tree || :)
-		remote_url="http://downloads.digium.com/pub/telephony/${directory_name:-${module_name}}/asterisk-${major_version}/x86-${host_bits}"
+		remote_url="https://downloads.digium.com/pub/telephony/${directory_name:-${module_name}}/asterisk-${major_version}/x86-${host_bits}"
 	fi
 fi
 
diff --git a/build_tools/make_version b/build_tools/make_version
index 9dca4bd9dce347b597b5300c7dd64361adcc18da..e97897b701edac091326a091d6fcc88f0aa0eb87 100755
--- a/build_tools/make_version
+++ b/build_tools/make_version
@@ -1,213 +1,67 @@
 #!/bin/sh
 
-AWK=${AWK:-awk}
 GIT=${GIT:-git}
-GREP=${GREP:-grep}
 SED=${SED:-sed}
-
+AWK=${AWK:-awk}
 
 if [ -f ${1}/.version ]; then
     cat ${1}/.version
-elif [ -d ${1}/.svn ]; then
-    PARTS=`LANG=C svn info ${1} | ${GREP} URL | ${AWK} '{print $2;}' | ${SED} -e 's:^.*/svn/asterisk/::' | ${SED} -e 's:/: :g'`
-    BRANCH=0
-    TEAM=0
-    TAG=0
-    FEATURE=0
-
-    REV=`svnversion -c ${1} | cut -d: -f2`
-
-    INTEGRATED=`LANG=C svn pg automerge-propname ${1}`
-    if [ -z "${INTEGRATED}" ] ; then
-        INTEGRATED=svnmerge-integrated
-    fi
-
-    BASE=`LANG=C svn pg ${INTEGRATED} ${1} | cut -d: -f1`
-
-    if [ "${PARTS}" = "trunk" ] ; then
-        echo SVN-trunk-r${REV}
-        exit 0
-    fi
-
-    for PART in $PARTS ; do
-        if [ ${TAG} != 0 ] ; then
-            if [ "${PART}" = "autotag_for_be" ] ; then
-                continue
-            fi
-            if [ "${PART}" = "autotag_for_sx00i" ] ; then
-                continue
-            fi
-            RESULT="${PART}"
-            break
-        fi
+    exit 0
+fi
 
-        if [ ${BRANCH} != 0 ] ; then
-            RESULT="${RESULT}-${PART}"
-            if [ ${FEATURE} != 0 ] ; then
-                RESULT="${RESULT}-${FEATURE_NAME}"
-            fi
-            break
-        fi
+if [ ! -d ${1}/.git ]; then
+    echo "UNKNOWN__and_probably_unsupported"
+    exit 0
+fi
 
-        if [ ${TEAM} != 0 ] ; then
-            if [ -z "${RESULT}" ] ; then
-                RESULT="${PART}"
-            else
-                RESULT="${RESULT}-${PART}"
-            fi
-            continue
-        fi
+if [ -z ${GIT} ]; then
+    GIT="git"
+fi
 
-        if [ "${PART}" = "certified" ] ; then
-            FEATURE=1
-            FEATURE_NAME="cert"
-            continue
-        fi
+if ! command -v ${GIT} >/dev/null 2>&1; then
+    echo "UNKNOWN__and_probably_unsupported"
+    exit 1
+fi
 
-        if [ "${PART}" = "branches" ] ; then
-            BRANCH=1
-            RESULT="branch"
-            continue
-        fi
+GITCHECK=$(${GIT} describe --always 2>/dev/null || echo gitfail 2>/dev/null)
+if [ "x${GITCHECK}" = "xgitfail" ]; then
+    echo "UNKNOWN__git_check_fail"
+    exit 1
+fi
 
-        if [ "${PART}" = "tags" ] ; then
-            TAG=1
-            continue
-        fi
+cd ${1} || exit 1
 
-        if [ "${PART}" = "team" ] ; then
-            TEAM=1
-            continue
-        fi
-    done
+MODIFIED=""
 
-    if [ ${TAG} != 0 ] ; then
-        echo ${RESULT}
-    else
-        echo SVN-${RESULT}-r${REV}${BASE:+-${BASE}}
-    fi
-elif [ -d ${1}/.git ]; then
-    if [ -z ${GIT} ]; then
-        GIT="git"
+# If MAINLINE_BRANCH is already set in the environment, use it.
+if [ -z "${MAINLINE_BRANCH}" ] ; then
+    # Try to retrieve MAINLINE_BRANCH from a local .develvars file first.
+    # .develvars is keyed by the branch name so we need to get that first.
+    BRANCH=$(${GIT} symbolic-ref --short HEAD 2>/dev/null)
+    if [ -f .develvars ] ; then
+        MAINLINE_BRANCH=$(${GIT} config -f .develvars --get branch.${BRANCH}.mainline-branch)
     fi
 
-    if ! command -v ${GIT} >/dev/null 2>&1; then
-        echo "UNKNOWN__and_probably_unsupported"
-        exit 1
+    # If we didn't find it, see if this is a well-known development branch.
+    # development/<mainline_branch>/<branchname> or
+    # devel/<mainline_branch>/<branchname>
+    if [ "x${MAINLINE_BRANCH}" = "x" ] ; then
+        MAINLINE_BRANCH=$(echo "${BRANCH}" | ${SED} -n -r -e "s@devel(opment)?/([0-9]+)/.+@\2@p")
     fi
-    cd ${1}
-
-    # If the first log commit messages indicates that this is checked into
-    # subversion, we'll just use the SVN- form of the revision.
-    MODIFIED=""
-    SVN_REV=`${GIT} log --pretty=full -1 | ${SED} -n '/git-svn-id:/ s/.*\@\([^ ]*\) .*/\1/p'`
-    if [ -z "$SVN_REV" ]; then
-        # If MAINLINE_BRANCH is already set in the environment, use it.
-        if [ -z "${MAINLINE_BRANCH}" ] ; then
-            # Try to retrieve MAINLINE_BRANCH from a local .develvars file first.
-            # .develvars is keyed by the branch name so we need to get that first.
-            BRANCH=$(${GIT} symbolic-ref --short HEAD)
-            if [ -f .develvars ] ; then
-                MAINLINE_BRANCH=$(${GIT} config -f .develvars --get branch.${BRANCH}.mainline-branch)
-            fi
-
-            # If we didn't find it, see if this is a well-known development branch.
-            # development/<mainline_branch>/<branchname> or
-            # devel/<mainline_branch>/<branchname>
-            if [ "x${MAINLINE_BRANCH}" = "x" ] ; then
-                MAINLINE_BRANCH=$(echo "${BRANCH}" | ${SED} -n -r -e "s@devel(opment)?/([0-9]+)/.+@\2@p")
-            fi
-
-            # If we didn't find it, get it from .gitreview defaultbranch.
-            if [ "x${MAINLINE_BRANCH}" = "x" ] ; then
-                MAINLINE_BRANCH=$(${GIT} config -f .gitreview --get gerrit.defaultbranch)
-            fi
-        fi
-
-        VERSION=`${GIT} describe --long --always --tags --dirty=M 2> /dev/null`
-        if [ $? -ne 0 ]; then
-            if [ "`${GIT} ls-files -m | wc -l`" != "0" ]; then
-                MODIFIED="M"
-            fi
-            # Some older versions of git do not support all the above
-            # options.
-            VERSION=`${GIT} rev-parse --short --verify HEAD`${MODIFIED}
-        fi
-        echo GIT-${MAINLINE_BRANCH}-${VERSION}
-    else
-        PARTS=`LANG=C ${GIT} log --pretty=full | ${GREP} -F "git-svn-id:" | head -1 | ${AWK} '{print $2;}' | ${SED} -e s:^.*/svn/$2/:: | ${SED} -e 's:/: :g' | ${SED} -e 's/@.*$//g'`
-        BRANCH=0
-        TEAM=0
-        TAG=0
-        FEATURE=0
-
-        if [ "`${GIT} ls-files -m | wc -l`" != "0" ]; then
-            MODIFIED="M"
-        fi
-
-        for PART in $PARTS ; do
-            if [ ${TAG} != 0 ] ; then
-                if [ "${PART}" = "autotag_for_be" ] ; then
-                    continue
-                fi
-                if [ "${PART}" = "autotag_for_sx00i" ] ; then
-                    continue
-                fi
-                RESULT="${PART}"
-                break
-            fi
-
-            if [ ${BRANCH} != 0 ] ; then
-                RESULT="${RESULT}-${PART}"
-                if [ ${FEATURE} != 0 ] ; then
-                    RESULT="${RESULT}-${FEATURE_NAME}"
-                fi
-                break
-            fi
 
-            if [ ${TEAM} != 0 ] ; then
-                if [ -z "${RESULT}" ] ; then
-                    RESULT="${PART}"
-                else
-                    RESULT="${RESULT}-${PART}"
-                fi
-                continue
-            fi
-
-            if [ "${PART}" = "certified" ] ; then
-                FEATURE=1
-                FEATURE_NAME="cert"
-                continue
-            fi
-
-            if [ "${PART}" = "branches" ] ; then
-                BRANCH=1
-                RESULT="branch"
-                continue
-            fi
-
-            if [ "${PART}" = "tags" ] ; then
-                TAG=1
-                continue
-            fi
-
-            if [ "${PART}" = "team" ] ; then
-                TEAM=1
-                continue
-            fi
-
-            if [ "${PART}" = "trunk" ]; then
-                echo SVN-trunk-r${SVN_REV}${MODIFIED}
-                exit 0
-            fi
-        done
+    # If we didn't find it, get it from configure.ac.
+    if [ "x${MAINLINE_BRANCH}" = "x" ] ; then
+        MAINLINE_BRANCH=$(${AWK} '/AC_INIT/ { print substr($2, 2, length($2) - 3) }' configure.ac)
+    fi
+fi
 
-        if [ ${TAG} != 0 ] ; then
-            echo ${RESULT}
-        else
-            echo SVN-${RESULT##-}-r${SVN_REV}${MODIFIED}
-        fi
+VERSION=`${GIT} describe --long --always --tags --dirty=M 2> /dev/null`
+if [ $? -ne 0 ]; then
+    if [ "`${GIT} ls-files -m | wc -l`" != "0" ]; then
+        MODIFIED="M"
     fi
-else
-    echo "UNKNOWN__and_probably_unsupported"
+    # Some older versions of git do not support all the above
+    # options.
+    VERSION=`${GIT} rev-parse --short --verify HEAD`${MODIFIED}
 fi
+echo GIT-${MAINLINE_BRANCH}-${VERSION}
diff --git a/build_tools/menuselect-deps.in b/build_tools/menuselect-deps.in
index f66d7bd64d3c9608806cde9cfeab4b19de94d7a0..b97341d52d8a6e30a38a20cb30900053930f38a3 100644
--- a/build_tools/menuselect-deps.in
+++ b/build_tools/menuselect-deps.in
@@ -23,8 +23,6 @@ ICONV=@PBX_ICONV@
 IKSEMEL=@PBX_IKSEMEL@
 IMAP_TK=@PBX_IMAP_TK@
 IODBC=@PBX_IODBC@
-ISDNNET=@PBX_ISDNNET@
-IXJUSER=@PBX_IXJUSER@
 JACK=@PBX_JACK@
 JANSSON=@PBX_JANSSON@
 URIPARSER=@PBX_URIPARSER@
@@ -36,9 +34,7 @@ LIBXSLT=@PBX_LIBXSLT@
 XMLSTARLET=@PBX_XMLSTARLET@
 BASH=@PBX_BASH@
 LUA=@PBX_LUA@
-MISDN=@PBX_MISDN@
 MYSQLCLIENT=@PBX_MYSQLCLIENT@
-NBS=@PBX_NBS@
 NETSNMP=@PBX_NETSNMP@
 NEWT=@PBX_NEWT@
 NEON=@PBX_NEON@
@@ -47,7 +43,6 @@ OGG=@PBX_OGG@
 OPUS=@PBX_OPUS@
 OPUSFILE=@PBX_OPUSFILE@
 OSPTK=@PBX_OSPTK@
-OSS=@PBX_OSS@
 PGSQL=@PBX_PGSQL@
 PJPROJECT=@PBX_PJPROJECT@
 POPT=@PBX_POPT@
@@ -64,17 +59,14 @@ SPEEX=@PBX_SPEEX@
 SPEEXDSP=@PBX_SPEEXDSP@
 SPEEX_PREPROCESS=@PBX_SPEEX_PREPROCESS@
 SQLITE3=@PBX_SQLITE3@
-SQLITE=@PBX_SQLITE@
 SRTP=@PBX_SRTP@
 SS7=@PBX_SS7@
 OPENSSL=@PBX_OPENSSL@
-SUPPSERV=@PBX_SUPPSERV@
 SYSLOG=@PBX_SYSLOG@
 TONEZONE=@PBX_TONEZONE@
 UNBOUND=@PBX_UNBOUND@
 UNIXODBC=@PBX_UNIXODBC@
 VORBIS=@PBX_VORBIS@
-VPB=@PBX_VPB@
 WINARCH=@PBX_WINARCH@
 ZLIB=@PBX_ZLIB@
 TIMERFD=@PBX_TIMERFD@
diff --git a/cdr/cdr_adaptive_odbc.c b/cdr/cdr_adaptive_odbc.c
index 4a10ec8142366c336d1897c96b7f9129aefa4e35..90032e26e5649a68b36462021f669b93a3ef018b 100644
--- a/cdr/cdr_adaptive_odbc.c
+++ b/cdr/cdr_adaptive_odbc.c
@@ -564,6 +564,7 @@ static int odbc_log(struct ast_cdr *cdr)
 					break;
 				case SQL_TYPE_TIMESTAMP:
 				case SQL_TIMESTAMP:
+				case SQL_DATETIME:
 					if (ast_strlen_zero(colptr)) {
 						continue;
 					} else {
diff --git a/cdr/cdr_beanstalkd.c b/cdr/cdr_beanstalkd.c
index f5e1c4b912d50ec761ec52e4aa0f367c652e528e..524274ffb765442bea0511483d5d822c93f52d72 100644
--- a/cdr/cdr_beanstalkd.c
+++ b/cdr/cdr_beanstalkd.c
@@ -195,10 +195,7 @@ static int beanstalk_put(struct ast_cdr *cdr) {
 
 	ast_rwlock_unlock(&config_lock);
 
-	t_cdr_json = ast_json_pack("{s:s, s:s, s:s, s:s, s:s, s:s, s:s, s:s, s:s, s:s, s:s, s:s, s:i, s:i, s:s, s:s, s:s, s:i, s:s, s:s,
-							   s:i, s:s, s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i,
-							   s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i, s:i,
-							   s:i, s:i, s:s}",
+	t_cdr_json = ast_json_pack("{s:s, s:s, s:s, s:s, s:s, s:s, s:s, s:s, s:s, s:s, s:s, s:s, s:i, s:i, s:s, s:s, s:s, s:s}",
 							   "AccountCode", S_OR(cdr->accountcode, ""),
 							   "Source", S_OR(cdr->src, ""),
 							   "Destination", S_OR(cdr->dst, ""),
@@ -216,42 +213,6 @@ static int beanstalk_put(struct ast_cdr *cdr) {
 							   "Disposition", S_OR(ast_cdr_disp2str(cdr->disposition), ""),
 							   "AMAFlags", S_OR(ast_channel_amaflags2string(cdr->amaflags), ""),
 							   "UniqueID", S_OR(cdr->uniqueid, ""),
-							   "SessionId", cdr->sessionId,
-							   "SIPSessionID", S_OR(cdr->SIPSessionID, ""),
-							   "sipIpAddress", S_OR(cdr->sipIpAddress, ""),
-							   "farEndIPAddress", S_OR(cdr->farEndIPAddress, ""),
-							   "sipResponseCode", cdr->sipResponseCode,
-							   "codec", S_OR(cdr->codec, ""),
-							   "localBurstDensity", cdr->rtp_stats->localBurstDensity,
-							   "remoteBurstDensity", cdr->rtp_stats->remoteBurstDensity,
-							   "localBurstDuration", cdr->rtp_stats->localBurstDuration,
-							   "remoteBurstDuration", cdr->rtp_stats->remoteBurstDuration,
-							   "localGapDensity", cdr->rtp_stats->localGapDensity,
-							   "remoteGapDensity", cdr->rtp_stats->remoteGapDensity,
-							   "localGapDuration", cdr->rtp_stats->localGapDuration,
-							   "remoteGapDuration", cdr->rtp_stats->remoteGapDuration,
-							   "localJbRate", cdr->rtp_stats->localJbRate,
-							   "remoteJbRate", cdr->rtp_stats->remoteJbRate,
-							   "localJbMax", cdr->rtp_stats->localJbMax,
-							   "remoteJbMax", cdr->rtp_stats->remoteJbMax,
-							   "localJbNominal", cdr->rtp_stats->localJbNominal,
-							   "remoteJbNominal", cdr->rtp_stats->remoteJbNominal,
-							   "localJbAbsMax", cdr->rtp_stats->localJbAbsMax,
-							   "remoteJbAbsMax", cdr->rtp_stats->remoteJbAbsMax,
-							   "jbAvg", cdr->rtp_stats->jbAvg,
-							   "localLossRate", cdr->rtp_stats->localLossRate,
-							   "remoteLossRate", cdr->rtp_stats->remoteLossRate,
-							   "discarded", cdr->rtp_stats->discarded,
-							   "lost", cdr->rtp_stats->lost,
-							   "rxpkts", cdr->rtp_stats->rxpkts,
-							   "txpkts", cdr->rtp_stats->txpkts,
-							   "jitter", cdr->rtp_stats->jitter,
-							   "maxJitter", cdr->rtp_stats->maxJitter,
-							   "averageRoundTripDelay", cdr->rtp_stats->averageRoundTripDelay,
-							   "farEndInterarrivalJitter", cdr->rtp_stats->farEndInterarrivalJitter,
-							   "averageFarEndInterarrivalJitter", cdr->rtp_stats->averageFarEndInterarrivalJitter,
-							   "receiveInterarrivalJitter", cdr->rtp_stats->receiveInterarrivalJitter,
-							   "averageReceiveInterarrivalJitter", cdr->rtp_stats->averageReceiveInterarrivalJitter,
 							   "UserField", S_OR(cdr->userfield, ""));
 
 	cdr_buffer = ast_json_dump_string(t_cdr_json);
diff --git a/cdr/cdr_odbc.c b/cdr/cdr_odbc.c
index f606a6d11249cad2b1ab4841cc466e132b382c52..51515c70e9f2d22744a0b795d739001722e0ff5b 100644
--- a/cdr/cdr_odbc.c
+++ b/cdr/cdr_odbc.c
@@ -146,48 +146,10 @@ static SQLHSTMT execute_cb(struct odbc_obj *obj, void *data)
 
 	if (ast_test_flag(&config, CONFIG_NEWCDRCOLUMNS)) {
 		SQLBindParameter(stmt, i, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->peeraccount), 0, cdr->peeraccount, 0, NULL);
-		SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->linkedid), 0, cdr->linkedid, 0, NULL);
-		SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->sequence, 0, NULL);
+		SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->linkedid), 0, cdr->linkedid, 0, NULL);
+		SQLBindParameter(stmt, i + 2, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->sequence, 0, NULL);
 	}
 
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->sessionId, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->SIPSessionID), 0, cdr->SIPSessionID, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->sipIpAddress), 0, cdr->sipIpAddress, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->farEndIPAddress), 0, cdr->farEndIPAddress, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->sipResponseCode, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(cdr->codec), 0, cdr->codec, 0, NULL);
-
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->localBurstDensity, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->remoteBurstDensity, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->localBurstDuration, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->remoteBurstDuration, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->localGapDensity, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->remoteGapDensity, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->localGapDuration, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->remoteGapDuration, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->localJbRate, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->remoteJbRate, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->localJbMax, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->remoteJbMax, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->localJbNominal, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->remoteJbNominal, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->localJbAbsMax, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->remoteJbAbsMax, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->jbAvg, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->localLossRate, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->remoteLossRate, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->discarded, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->lost, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->rxpkts, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->txpkts, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->jitter, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->maxJitter, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->averageRoundTripDelay, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->farEndInterarrivalJitter, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->averageFarEndInterarrivalJitter, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->receiveInterarrivalJitter, 0, NULL);
-	SQLBindParameter(stmt, i++, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &cdr->rtp_stats->averageReceiveInterarrivalJitter, 0, NULL);
-
 	ODBC_res = ast_odbc_execute_sql(obj, stmt, sqlcmd);
 
 	if ((ODBC_res != SQL_SUCCESS) && (ODBC_res != SQL_SUCCESS_WITH_INFO)) {
diff --git a/cdr/cdr_radius.c b/cdr/cdr_radius.c
index ebbf1743df8ba194895c2634fc18556349dfc4c1..0a5fa6371c98358a1b6f598d7442a4f8178949e1 100644
--- a/cdr/cdr_radius.c
+++ b/cdr/cdr_radius.c
@@ -69,43 +69,7 @@ enum {
 	PW_AST_DISPOSITION =  115,
 	PW_AST_AMA_FLAGS =    116,
 	PW_AST_UNIQUE_ID =    117,
-	PW_AST_USER_FIELD =   118,
-	PW_AST_USER_SESSION_ID =   119,
-	PW_AST_USER_SESSION_ID2 =   120,
-	PW_AST_SIP_IP_ADDR =   121,
-	PW_AST_FAR_END_IP_ADDR =   122,
-	PW_AST_SIP_RESPONSE_CODE =   123,
-	PW_AST_CODEC =   124,
-	PW_AST_RTP_LOCAL_BURST_DENSITY =   125,
-	PW_AST_RTP_REMOTE_BURST_DENSITY =   126,
-	PW_AST_RTP_LOCAL_BURST_DURATION =   127,
-	PW_AST_RTP_REMOTE_BURST_DURATION =   128,
-	PW_AST_RTP_LOCAL_GAP_DENSITY =   129,
-	PW_AST_RTP_REMOTE_GAP_DENSITY =   130,
-	PW_AST_RTP_LOCAL_GAP_DURATION =   131,
-	PW_AST_RTP_REMOTE_GAP_DURATION =   132,
-	PW_AST_RTP_LOCAL_JB_RATE =   133,
-	PW_AST_RTP_REMOTE_JB_RATE =   134,
-	PW_AST_RTP_LOCAL_JB_MAX =   135,
-	PW_AST_RTP_REMOTE_JB_MAX =   136,
-	PW_AST_RTP_LOCAL_JB_NOMINAL =   137,
-	PW_AST_RTP_REMOTE_JB_NOMINAL =   138,
-	PW_AST_RTP_LOCAL_JB_ABS_MAX =   139,
-	PW_AST_RTP_REMOTE_JB_ABS_MAX =   140,
-	PW_AST_RTP_JB_AVG =   141,
-	PW_AST_RTP_LOCAL_LOSS_RATE =   142,
-	PW_AST_RTP_REMOTE_LOSS_RATE =   143,
-	PW_AST_RTP_DISCARDED =   144,
-	PW_AST_RTP_LOST =   145,
-	PW_AST_RTP_RX_PKTS =   146,
-	PW_AST_RTP_TX_PKTS =   147,
-	PW_AST_RTP_JITTER =   148,
-	PW_AST_RTP_MAX_JITTER =   149,
-	PW_AST_RTP_AVERAGE_ROUND_TRIP_DELAY =   150,
-	PW_AST_RTP_FAR_END_INTERARRIVAL_JITTER =   151,
-	PW_AST_RTP_AVERAGE_FAR_END_INTERARRIVAL_JITTER =   152,
-	PW_AST_RTP_RECEIVE_INTERARRIVAL_JITTER =   153,
-	PW_AST_RTP_AVERAGE_RECEIVE_INTERARRIVAL_JITTER =   154
+	PW_AST_USER_FIELD =   118
 };
 
 enum {
@@ -229,92 +193,6 @@ static int build_radius_record(VALUE_PAIR **tosend, struct ast_cdr *cdr)
 			return -1;
 	}
 
-	/* SessionId */
-	if (!rc_avpair_add(rh, tosend, PW_AST_USER_SESSION_ID, &cdr->sessionId, 0, VENDOR_CODE))
-		return -1;
-
-	/* SIPSessionID */
-	if (!rc_avpair_add(rh, tosend, PW_AST_USER_SESSION_ID2, &cdr->SIPSessionID, strlen(cdr->SIPSessionID), VENDOR_CODE))
-		return -1;
-
-	/* SIP IP Address */
-	if (!rc_avpair_add(rh, tosend, PW_AST_SIP_IP_ADDR, &cdr->sipIpAddress, strlen(cdr->sipIpAddress), VENDOR_CODE))
-		return -1;
-
-	/* Far End IP Address */
-	if (!rc_avpair_add(rh, tosend, PW_AST_FAR_END_IP_ADDR, &cdr->farEndIPAddress, strlen(cdr->farEndIPAddress), VENDOR_CODE))
-		return -1;
-
-	/* Sip Response Code */
-	if (!rc_avpair_add(rh, tosend, PW_AST_SIP_RESPONSE_CODE, &cdr->sipResponseCode, 0, VENDOR_CODE))
-		return -1;
-
-	/* codec */
-	if (!rc_avpair_add(rh, tosend, PW_AST_CODEC, &cdr->codec, strlen(cdr->codec), VENDOR_CODE))
-		return -1;
-
-	/* RTP statistics */
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_LOCAL_BURST_DENSITY, &cdr->rtp_stats->localBurstDensity, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_REMOTE_BURST_DENSITY, &cdr->rtp_stats->remoteBurstDensity, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_LOCAL_BURST_DURATION, &cdr->rtp_stats->localBurstDuration, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_REMOTE_BURST_DURATION, &cdr->rtp_stats->remoteBurstDuration, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_LOCAL_GAP_DENSITY, &cdr->rtp_stats->localGapDensity, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_REMOTE_GAP_DENSITY, &cdr->rtp_stats->remoteGapDensity, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_LOCAL_GAP_DURATION, &cdr->rtp_stats->localGapDuration, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_REMOTE_GAP_DURATION, &cdr->rtp_stats->remoteGapDuration, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_LOCAL_JB_RATE, &cdr->rtp_stats->localJbRate, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_REMOTE_JB_RATE, &cdr->rtp_stats->remoteJbRate, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_LOCAL_JB_MAX, &cdr->rtp_stats->localJbMax, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_REMOTE_JB_MAX, &cdr->rtp_stats->remoteJbMax, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_LOCAL_JB_NOMINAL, &cdr->rtp_stats->localJbNominal, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_REMOTE_JB_NOMINAL, &cdr->rtp_stats->remoteJbNominal, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_LOCAL_JB_ABS_MAX, &cdr->rtp_stats->localJbAbsMax, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_REMOTE_JB_ABS_MAX, &cdr->rtp_stats->remoteJbAbsMax, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_JB_AVG, &cdr->rtp_stats->jbAvg, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_LOCAL_LOSS_RATE, &cdr->rtp_stats->localLossRate, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_REMOTE_LOSS_RATE, &cdr->rtp_stats->remoteLossRate, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_DISCARDED, &cdr->rtp_stats->discarded, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_LOST, &cdr->rtp_stats->lost, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_RX_PKTS, &cdr->rtp_stats->rxpkts, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_TX_PKTS, &cdr->rtp_stats->txpkts, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_JITTER, &cdr->rtp_stats->jitter, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_MAX_JITTER, &cdr->rtp_stats->maxJitter, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_AVERAGE_ROUND_TRIP_DELAY, &cdr->rtp_stats->averageRoundTripDelay, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_FAR_END_INTERARRIVAL_JITTER, &cdr->rtp_stats->farEndInterarrivalJitter, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_AVERAGE_FAR_END_INTERARRIVAL_JITTER, &cdr->rtp_stats->averageFarEndInterarrivalJitter, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_RECEIVE_INTERARRIVAL_JITTER, &cdr->rtp_stats->receiveInterarrivalJitter, 0, VENDOR_CODE))
-		return -1;
-	if (!rc_avpair_add(rh, tosend, PW_AST_RTP_AVERAGE_RECEIVE_INTERARRIVAL_JITTER, &cdr->rtp_stats->averageReceiveInterarrivalJitter, 0, VENDOR_CODE))
-		return -1;
-
 	/* Setting Acct-Session-Id & User-Name attributes for proper generation
 	 * of Acct-Unique-Session-Id on server side
 	 */
diff --git a/cdr/cdr_syslog.c b/cdr/cdr_syslog.c
deleted file mode 100644
index 2d371da6ac5b6e8c87d5dae2b041cf964978643e..0000000000000000000000000000000000000000
--- a/cdr/cdr_syslog.c
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2009, malleable, LLC.
- *
- * Sean Bright <sean@malleable.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*!
- * \file
- * \brief syslog CDR logger
- *
- * \author Sean Bright <sean@malleable.com>
- * \ingroup cdr_drivers
- */
-
-/*! \li \ref cdr_syslog.c uses the configuration file \ref cdr_syslog.conf
- * \addtogroup configuration_file Configuration Files
- */
-
-/*!
- * \page cdr_syslog.conf cdr_syslog.conf
- * \verbinclude cdr_syslog.conf.sample
- */
-
-/*** MODULEINFO
-	<depend>syslog</depend>
-	<defaultenabled>no</defaultenabled>
-	<support_level>deprecated</support_level>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
-***/
-
-#include "asterisk.h"
-
-#include "asterisk/module.h"
-#include "asterisk/lock.h"
-#include "asterisk/cdr.h"
-#include "asterisk/pbx.h"
-
-#include <syslog.h>
-
-#include "asterisk/syslog.h"
-
-static const char CONFIG[] = "cdr_syslog.conf";
-
-AST_THREADSTORAGE(syslog_buf);
-
-static const char name[] = "cdr-syslog";
-
-struct cdr_syslog_config {
-	AST_DECLARE_STRING_FIELDS(
-		AST_STRING_FIELD(ident);
-		AST_STRING_FIELD(format);
-	);
-	int facility;
-	int priority;
-	ast_mutex_t lock;
-	AST_LIST_ENTRY(cdr_syslog_config) list;
-};
-
-static AST_RWLIST_HEAD_STATIC(sinks, cdr_syslog_config);
-
-static void free_config(void)
-{
-	struct cdr_syslog_config *sink;
-
-	while ((sink = AST_RWLIST_REMOVE_HEAD(&sinks, list))) {
-		ast_mutex_destroy(&sink->lock);
-		ast_string_field_free_memory(sink);
-		ast_free(sink);
-	}
-}
-
-static int syslog_log(struct ast_cdr *cdr)
-{
-	struct ast_channel *dummy;
-	struct ast_str *str;
-	struct cdr_syslog_config *sink;
-
-	/* Batching saves memory management here.  Otherwise, it's the same as doing an
-	   allocation and free each time. */
-	if (!(str = ast_str_thread_get(&syslog_buf, 16))) {
-		return -1;
-	}
-
-	if (!(dummy = ast_dummy_channel_alloc())) {
-		ast_log(AST_LOG_ERROR, "Unable to allocate channel for variable substitution.\n");
-		return -1;
-	}
-
-	/* We need to dup here since the cdr actually belongs to the other channel,
-	   so when we release this channel we don't want the CDR getting cleaned
-	   up prematurely. */
-	ast_channel_cdr_set(dummy, ast_cdr_dup(cdr));
-
-	AST_RWLIST_RDLOCK(&sinks);
-
-	AST_LIST_TRAVERSE(&sinks, sink, list) {
-
-		ast_str_substitute_variables(&str, 0, dummy, sink->format);
-
-		/* Even though we have a lock on the list, we could be being chased by
-		   another thread and this lock ensures that we won't step on anyone's
-		   toes.  Once each CDR backend gets it's own thread, this lock can be
-		   removed. */
-		ast_mutex_lock(&sink->lock);
-
-		openlog(sink->ident, LOG_CONS, sink->facility);
-		syslog(sink->priority, "%s", ast_str_buffer(str));
-		closelog();
-
-		ast_mutex_unlock(&sink->lock);
-	}
-
-	AST_RWLIST_UNLOCK(&sinks);
-
-	ast_channel_unref(dummy);
-
-	return 0;
-}
-
-static int load_config(int reload)
-{
-	struct ast_config *cfg;
-	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
-	int default_facility = LOG_LOCAL4;
-	int default_priority = LOG_INFO;
-	const char *catg = NULL, *tmp;
-
-	cfg = ast_config_load(CONFIG, config_flags);
-	if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) {
-		ast_log(AST_LOG_ERROR,
-			"Unable to load %s. Not logging custom CSV CDRs to syslog.\n", CONFIG);
-		return -1;
-	} else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
-		return 0;
-	}
-
-	if (reload) {
-		free_config();
-	}
-
-	if (!(ast_strlen_zero(tmp = ast_variable_retrieve(cfg, "general", "facility")))) {
-		int facility = ast_syslog_facility(tmp);
-		if (facility < 0) {
-			ast_log(AST_LOG_WARNING,
-				"Invalid facility '%s' specified, defaulting to '%s'\n",
-				tmp, ast_syslog_facility_name(default_facility));
-		} else {
-			default_facility = facility;
-		}
-	}
-
-	if (!(ast_strlen_zero(tmp = ast_variable_retrieve(cfg, "general", "priority")))) {
-		int priority = ast_syslog_priority(tmp);
-		if (priority < 0) {
-			ast_log(AST_LOG_WARNING,
-				"Invalid priority '%s' specified, defaulting to '%s'\n",
-				tmp, ast_syslog_priority_name(default_priority));
-		} else {
-			default_priority = priority;
-		}
-	}
-
-	while ((catg = ast_category_browse(cfg, catg))) {
-		struct cdr_syslog_config *sink;
-
-		if (!strcasecmp(catg, "general")) {
-			continue;
-		}
-
-		if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "template"))) {
-			ast_log(AST_LOG_WARNING,
-				"No 'template' parameter found for '%s'.  Skipping.\n", catg);
-			continue;
-		}
-
-		sink = ast_calloc_with_stringfields(1, struct cdr_syslog_config, 1024);
-
-		if (!sink) {
-			ast_log(AST_LOG_ERROR,
-				"Unable to allocate memory for configuration settings.\n");
-			free_config();
-			break;
-		}
-
-		ast_mutex_init(&sink->lock);
-		ast_string_field_set(sink, ident, catg);
-		ast_string_field_set(sink, format, tmp);
-
-		if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "facility"))) {
-			sink->facility = default_facility;
-		} else {
-			int facility = ast_syslog_facility(tmp);
-			if (facility < 0) {
-				ast_log(AST_LOG_WARNING,
-					"Invalid facility '%s' specified for '%s,' defaulting to '%s'\n",
-					tmp, catg, ast_syslog_facility_name(default_facility));
-			} else {
-				sink->facility = facility;
-			}
-		}
-
-		if (ast_strlen_zero(tmp = ast_variable_retrieve(cfg, catg, "priority"))) {
-			sink->priority = default_priority;
-		} else {
-			int priority = ast_syslog_priority(tmp);
-			if (priority < 0) {
-				ast_log(AST_LOG_WARNING,
-					"Invalid priority '%s' specified for '%s,' defaulting to '%s'\n",
-					tmp, catg, ast_syslog_priority_name(default_priority));
-			} else {
-				sink->priority = priority;
-			}
-		}
-
-		AST_RWLIST_INSERT_TAIL(&sinks, sink, list);
-	}
-
-	ast_config_destroy(cfg);
-
-	return AST_RWLIST_EMPTY(&sinks) ? -1 : 0;
-}
-
-static int unload_module(void)
-{
-	if (ast_cdr_unregister(name)) {
-		return -1;
-	}
-
-	if (AST_RWLIST_WRLOCK(&sinks)) {
-		ast_cdr_register(name, ast_module_info->description, syslog_log);
-		ast_log(AST_LOG_ERROR, "Unable to lock sink list.  Unload failed.\n");
-		return -1;
-	}
-
-	free_config();
-	AST_RWLIST_UNLOCK(&sinks);
-	return 0;
-}
-
-static enum ast_module_load_result load_module(void)
-{
-	int res;
-
-	if (AST_RWLIST_WRLOCK(&sinks)) {
-		ast_log(AST_LOG_ERROR, "Unable to lock sink list.  Load failed.\n");
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	res = load_config(0);
-	AST_RWLIST_UNLOCK(&sinks);
-	if (res) {
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	ast_cdr_register(name, ast_module_info->description, syslog_log);
-	return AST_MODULE_LOAD_SUCCESS;
-}
-
-static int reload(void)
-{
-	int res;
-	if (AST_RWLIST_WRLOCK(&sinks)) {
-		ast_log(AST_LOG_ERROR, "Unable to lock sink list.  Load failed.\n");
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	if ((res = load_config(1))) {
-		free_config();
-	}
-
-	AST_RWLIST_UNLOCK(&sinks);
-
-	return res ? AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Customizable syslog CDR Backend",
-	.support_level = AST_MODULE_SUPPORT_CORE,
-	.load = load_module,
-	.unload = unload_module,
-	.reload = reload,
-	.load_pri = AST_MODPRI_CDR_DRIVER,
-	.requires = "cdr",
-);
diff --git a/cdr/cdr_tds.c b/cdr/cdr_tds.c
index be00789688584e3365f90420899c7f685e5dd59e..4a661532054f9805a833c7d8511995fe89ae77d2 100644
--- a/cdr/cdr_tds.c
+++ b/cdr/cdr_tds.c
@@ -186,24 +186,13 @@ retry:
 					 "("
 					 "'%s', '%s', '%s', '%s', '%s', '%s', "
 					 "'%s', '%s', '%s', %s, %s, %s, %ld, "
-					 "%ld, '%s', '%s', '%s', %ld , '%s' "
-					 "'%s', %ld , '%s', %ld , %ld , %ld ,
-					 %ld , %ld , %ld , %ld , %ld , %ld ,
-					 %ld , %ld , %ld , %ld , %ld , %ld ,
-					 %ld , %ld , %ld , %ld , %ld , %ld ,
-					 %ld , %ld , %ld , %ld , %ld , %ld ,
-					 %ld, '%s')",
+					 "%ld, '%s', '%s', '%s', '%s'"
+					 ")",
 					 settings->table,
 					 accountcode, src, dst, dcontext, clid, channel,
 					 dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
 					 cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid,
-					 cdr->sessionId, cdr->sipIpAddress, cdr->farEndIPAddress, cdr->sipResponseCode, cdr->codec, cdr->rtp_stats->localBurstDensity,
-					 cdr->rtp_stats->remoteBurstDensity, cdr->rtp_stats->localBurstDuration, cdr->rtp_stats->remoteBurstDuration, cdr->rtp_stats->localGapDensity, cdr->rtp_stats->remoteGapDensity,
-					 cdr->rtp_stats->localGapDuration, cdr->rtp_stats->remoteGapDuration, cdr->rtp_stats->localJbRate, cdr->rtp_stats->remoteJbRate, cdr->rtp_stats->localJbMax,
-					 cdr->rtp_stats->remoteJbMax, cdr->rtp_stats->localJbNominal, cdr->rtp_stats->remoteJbNominal, cdr->rtp_stats->localJbAbsMax, cdr->rtp_stats->remoteJbAbsMax,
-					 cdr->rtp_stats->jbAvg, cdr->rtp_stats->localLossRate, cdr->rtp_stats->remoteLossRate, cdr->rtp_stats->discarded, cdr->rtp_stats->lost, cdr->rtp_stats->rxpkts,
-					 cdr->rtp_stats->txpkts, cdr->rtp_stats->jitter, cdr->rtp_stats->maxJitter, cdr->rtp_stats->averageRoundTripDelay, cdr->rtp_stats->farEndInterarrivalJitter
-					 cdr->rtp_stats->averageFarEndInterarrivalJitter, cdr->rtp_stats->receiveInterarrivalJitter, cdr->rtp_stats->averageReceiveInterarrivalJitter, userfield
+					 userfield
 			);
 		}
 	} else {
@@ -246,23 +235,12 @@ retry:
 					 "("
 					 "'%s', '%s', '%s', '%s', '%s', '%s', "
 					 "'%s', '%s', '%s', %s, %s, %s, %ld, "
-					 "%ld, '%s', '%s', %ld , '%s', '%s', %ld, "
-					 "'%s', %ld, %ld, %ld, %ld, %ld, %ld, %ld,
-					 %ld, %ld, %ld, %ld, %ld, %ld, %ld, %ld, %ld,
-					 %ld, %ld, %ld, %ld, %ld, %ld, %ld, %ld, %ld,
-					 %ld, %ld, %ld, '%s')",
+					 "%ld, '%s', '%s', '%s'"
+					 ")",
 					 settings->table,
 					 accountcode, src, dst, dcontext, clid, channel,
 					 dstchannel, lastapp, lastdata, start, answer, end, cdr->duration,
-					 cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags),
-					 cdr->sessionId, cdr->sipIpAddress, cdr->farEndIPAddress, cdr->sipResponseCode, cdr->codec,
-					 cdr->rtp_stats->localBurstDensity, cdr->rtp_stats->remoteBurstDensity, cdr->rtp_stats->localBurstDuration, cdr->rtp_stats->remoteBurstDuration,
-					 cdr->rtp_stats->localGapDensity, cdr->rtp_stats->remoteGapDensity, cdr->rtp_stats->localGapDuration, cdr->rtp_stats->remoteGapDuration,
-					 cdr->rtp_stats->localJbRate, cdr->rtp_stats->remoteJbRate, cdr->rtp_stats->localJbMax, cdr->rtp_stats->remoteJbMax, cdr->rtp_stats->localJbNominal,
-					 cdr->rtp_stats->remoteJbNominal, cdr->rtp_stats->localJbAbsMax, cdr->rtp_stats->remoteJbAbsMax, cdr->rtp_stats->jbAvg, cdr->rtp_stats->localLossRate,
-					 cdr->rtp_stats->remoteLossRate, cdr->rtp_stats->discarded, cdr->rtp_stats->lost, cdr->rtp_stats->rxpkts, cdr->rtp_stats->txpkts, cdr->rtp_stats->jitter,
-					 cdr->rtp_stats->maxJitter, cdr->rtp_stats->averageRoundTripDelay, cdr->rtp_stats->farEndInterarrivalJitter, cdr->rtp_stats->averageFarEndInterarrivalJitter,
-					 cdr->rtp_stats->receiveInterarrivalJitter, cdr->rtp_stats->averageReceiveInterarrivalJitter, uniqueid
+					 cdr->billsec, ast_cdr_disp2str(cdr->disposition), ast_channel_amaflags2string(cdr->amaflags), uniqueid
 			);
 		}
 	}
diff --git a/cel/cel_odbc.c b/cel/cel_odbc.c
index 12f4a0618d3578c10f3f3f3bb26c350236880ae2..f73eece70ac1115c0e6ff660aecb7378f24c8e77 100644
--- a/cel/cel_odbc.c
+++ b/cel/cel_odbc.c
@@ -606,6 +606,7 @@ static void odbc_log(struct ast_event *event)
 					break;
 				case SQL_TYPE_TIMESTAMP:
 				case SQL_TIMESTAMP:
+				case SQL_DATETIME:
 					if (ast_strlen_zero(colptr)) {
 						continue;
 					} else {
diff --git a/channels/Makefile b/channels/Makefile
index b641173fe85304fb7c7388b3eed6a3ccb1b7d51e..0af82d9ff0c4616d00aae2d175df16d2cad0a3ed 100644
--- a/channels/Makefile
+++ b/channels/Makefile
@@ -29,7 +29,6 @@ iax2/parser.o: _ASTCFLAGS+=$(call get_menuselect_cflags,MALLOC_DEBUG)
 $(call MOD_ADD_C,chan_sip,$(wildcard sip/*.c))
 $(call MOD_ADD_C,chan_pjsip,$(wildcard pjsip/*.c))
 $(call MOD_ADD_C,chan_dahdi,$(wildcard dahdi/*.c) sig_analog.c sig_pri.c sig_ss7.c)
-$(call MOD_ADD_C,chan_misdn,misdn_config.c misdn/isdn_lib.c misdn/isdn_msg_parser.c)
 
 chan_dahdi.o: _ASTCFLAGS+=$(call get_menuselect_cflags,LOTS_OF_SPANS)
 chan_mgcp.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
@@ -37,8 +36,4 @@ chan_unistim.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
 chan_phone.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
 chan_sip.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
 
-chan_misdn.o: _ASTCFLAGS+=-Imisdn
-misdn_config.o: _ASTCFLAGS+=-Imisdn
-misdn/isdn_lib.o: _ASTCFLAGS+=-Wno-strict-aliasing
-
-$(call MOD_ADD_C,chan_oss,console_video.c vgrabbers.c console_board.c)
+$(call MOD_ADD_C,console_video.c vgrabbers.c console_board.c)
diff --git a/channels/chan_alsa.c b/channels/chan_alsa.c
index 3bfa5c924deea77fcc53da9dd57f80023a900013..35bb34bd2483de4cb79e50ef89635e5da8904ddc 100644
--- a/channels/chan_alsa.c
+++ b/channels/chan_alsa.c
@@ -34,7 +34,9 @@
 
 /*** MODULEINFO
 	<depend>alsa</depend>
-	<support_level>extended</support_level>
+	<defaultenabled>no</defaultenabled>
+	<support_level>deprecated</support_level>
+	<replacement>chan_console</replacement>
 	<deprecated_in>19</deprecated_in>
 	<removed_in>21</removed_in>
  ***/
@@ -1038,7 +1040,7 @@ static int load_module(void)
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "ALSA Console Channel Driver",
-	.support_level = AST_MODULE_SUPPORT_EXTENDED,
+	.support_level = AST_MODULE_SUPPORT_DEPRECATED,
 	.load = load_module,
 	.unload = unload_module,
 	.load_pri = AST_MODPRI_CHANNEL_DRIVER,
diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c
index ad7a8e5e5491c46e81dec2075b7d506652d6f022..e49a7996a9fd930fdafa42142bf2742e4365fea5 100644
--- a/channels/chan_dahdi.c
+++ b/channels/chan_dahdi.c
@@ -177,6 +177,35 @@
 			<para>This application will Accept the R2 call either with charge or no charge.</para>
 		</description>
 	</application>
+	<function name="POLARITY" language="en_US">
+		<synopsis>
+			Set or get the polarity of a DAHDI channel.
+		</synopsis>
+		<syntax />
+		<description>
+			<para>The POLARITY function can be used to set the polarity of a DAHDI channel.</para>
+			<para>Applies only to FXS channels (using FXO signalling) with supporting hardware.</para>
+			<para>The polarity can be set to the following numeric or named values:</para>
+			<enumlist>
+				<enum name="0" />
+				<enum name="idle" />
+				<enum name="1" />
+				<enum name="reverse" />
+			</enumlist>
+			<para>However, when read, the function will always return 0 or 1.</para>
+			<example title="Set idle polarity">
+			same => n,Set(POLARITY()=0)
+			</example>
+			<example title="Set reverse polarity">
+			same => n,NoOp(Current Polarity: ${POLARITY()})
+			same => n,Set(POLARITY()=reverse)
+			same => n,NoOp(New Polarity: ${POLARITY()})
+			</example>
+			<example title="Reverse the polarity from whatever it is currently">
+			same => n,Set(POLARITY()=${IF($[ "${POLARITY()}" = "1" ]?0:1)})
+			</example>
+		</description>
+	</function>
 	<info name="CHANNEL" language="en_US" tech="DAHDI">
 		<enumlist>
 			<enum name="dahdi_channel">
@@ -232,7 +261,71 @@
 					completely disabled)</para>
 				<para>	<literal>voice</literal>	Voice mode (returns from FAX mode, reverting the changes that were made)</para>
 			</enum>
+			<enum name="dialmode">
+				<para>R/W Pulse and tone dialing mode of the channel.</para>
+				<para>If set, overrides the setting in <literal>chan_dahdi.conf</literal> for that channel.</para>
+				<enumlist>
+					<enum name="both" />
+					<enum name="pulse" />
+					<enum name="dtmf" />
+					<enum name="tone" />
+					<enum name="none" />
+				</enumlist>
+			</enum>
+		</enumlist>
+	</info>
+	<info name="Dial_Resource" language="en_US" tech="DAHDI">
+		<para>DAHDI allows several modifiers to be specified as part of the resource.</para>
+		<para>The general syntax is :</para>
+		<para><literal>Dial(DAHDI/pseudo[/extension])</literal></para>
+		<para><literal>Dial(DAHDI/&lt;channel#&gt;[c|r&lt;cadence#&gt;|d][/extension])</literal></para>
+		<para><literal>Dial(DAHDI/(g|G|r|R)&lt;group#(0-63)&gt;[c|r&lt;cadence#&gt;|d][/extension])</literal></para>
+		<para>The following modifiers may be used before the channel number:</para>
+		<enumlist>
+		<enum name="g">
+			<para>Search forward, dialing on first available channel in group (lowest to highest).</para>
+		</enum>
+		<enum name="G">
+			<para>Search backward, dialing on first available channel in group (highest to lowest).</para>
+		</enum>
+		<enum name="r">
+			<para>Round robin search forward, picking up from where last left off (lowest to highest).</para>
+		</enum>
+		<enum name="R">
+			<para>Round robin search backward, picking up from where last left off (highest to lowest).</para>
+		</enum>
+		</enumlist>
+		<para>The following modifiers may be used after the channel number:</para>
+		<enumlist>
+		<enum name="c">
+			<para>Wait for DTMF digit <literal>#</literal> before providing answer supervision.</para>
+			<para>This can be useful on outbound calls via FXO ports, as otherwise
+			they would indicate answer immediately.</para>
+		</enum>
+		<enum name="d">
+			<para>Force bearer capability for ISDN/SS7 call to digital.</para>
+		</enum>
+		<enum name="i">
+			<para>ISDN span channel restriction.</para>
+			<para>Used by CC to ensure that the CC recall goes out the same span.
+			Also to make ISDN channel names dialable when the sequence number
+			is stripped off.  (Used by DTMF attended transfer feature.)</para>
+		</enum>
+		<enum name="r">
+			<para>Specifies the distinctive ring cadence number to use immediately after
+			specifying this option. There are 4 default built-in cadences, and up to 24
+			total cadences may be configured.</para>
+		</enum>
 		</enumlist>
+		<example title="Dial 555-1212 on first available channel in group 1, searching from highest to lowest">
+		same => n,Dial(DAHDI/g1/5551212)
+		</example>
+		<example title="Ringing FXS channel 4 with ring cadence 2">
+		same => n,Dial(DAHDI/4r2)
+		</example>
+		<example title="Dial 555-1212 on channel 3 and require answer confirmation">
+		same => n,Dial(DAHDI/3c/5551212)
+		</example>
 	</info>
 	<manager name="DAHDITransfer" language="en_US">
 		<synopsis>
@@ -952,6 +1045,7 @@ static struct dahdi_chan_conf dahdi_chan_conf_default(void)
 			.mohsuggest = "",
 			.parkinglot = "",
 			.transfertobusy = 1,
+			.dialmode = 0,
 
 			.ani_info_digits = 2,
 			.ani_wink_time = 1000,
@@ -1560,19 +1654,29 @@ static int my_send_callerid(void *pvt, int cwcid, struct ast_party_caller *calle
 	}
 
 	if ((p->cidspill = ast_malloc(MAX_CALLERID_SIZE))) {
+		int pres = ast_party_id_presentation(&caller->id);
 		if (cwcid == 0) {
-			p->cidlen = ast_callerid_generate(p->cidspill,
+			p->cidlen = ast_callerid_full_generate(p->cidspill,
 				caller->id.name.str,
 				caller->id.number.str,
+				NULL,
+				-1,
+				pres,
+				0,
+				CID_TYPE_MDMF,
 				AST_LAW(p));
 		} else {
 			ast_verb(3, "CPE supports Call Waiting Caller*ID.  Sending '%s/%s'\n",
 				caller->id.name.str, caller->id.number.str);
 			p->callwaitcas = 0;
 			p->cidcwexpire = 0;
-			p->cidlen = ast_callerid_callwaiting_generate(p->cidspill,
+			p->cidlen = ast_callerid_callwaiting_full_generate(p->cidspill,
 				caller->id.name.str,
 				caller->id.number.str,
+				NULL,
+				-1,
+				pres,
+				0,
 				AST_LAW(p));
 			p->cidlen += READ_SIZE * 4;
 		}
@@ -2641,6 +2745,86 @@ static void my_hangup_polarityswitch(void *pvt)
 	}
 }
 
+/*! \brief Return DAHDI pivot if channel is FXO signalled */
+static struct dahdi_pvt *fxo_pvt(struct ast_channel *chan)
+{
+	int res;
+	struct dahdi_params dahdip;
+	struct dahdi_pvt *pvt = NULL;
+
+	if (strcasecmp(ast_channel_tech(chan)->type, "DAHDI")) {
+		ast_log(LOG_WARNING, "%s is not a DAHDI channel\n", ast_channel_name(chan));
+		return NULL;
+	}
+
+	memset(&dahdip, 0, sizeof(dahdip));
+	res = ioctl(ast_channel_fd(chan, 0), DAHDI_GET_PARAMS, &dahdip);
+
+	if (res) {
+		ast_log(LOG_WARNING, "Unable to get parameters of %s: %s\n", ast_channel_name(chan), strerror(errno));
+		return NULL;
+	}
+	if (!(dahdip.sigtype & __DAHDI_SIG_FXO)) {
+		ast_log(LOG_WARNING, "%s is not FXO signalled\n", ast_channel_name(chan));
+		return NULL;
+	}
+
+	pvt = ast_channel_tech_pvt(chan);
+	if (!dahdi_analog_lib_handles(pvt->sig, 0, 0)) {
+		ast_log(LOG_WARNING, "Channel signalling is not analog");
+		return NULL;
+	}
+
+	return pvt;
+}
+
+static int polarity_read(struct ast_channel *chan, const char *cmd, char *data, char *buffer, size_t buflen)
+{
+	struct dahdi_pvt *pvt;
+
+	pvt = fxo_pvt(chan);
+	if (!pvt) {
+		return -1;
+	}
+
+	snprintf(buffer, buflen, "%d", pvt->polarity);
+
+	return 0;
+}
+
+static int polarity_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
+{
+	struct dahdi_pvt *pvt;
+	int polarity;
+
+	pvt = fxo_pvt(chan);
+	if (!pvt) {
+		return -1;
+	}
+
+	if (!strcasecmp(value, "idle")) {
+		polarity = POLARITY_IDLE;
+	} else if (!strcasecmp(value, "reverse")) {
+		polarity = POLARITY_REV;
+	} else {
+		polarity = atoi(value);
+	}
+
+	if (polarity != POLARITY_IDLE && polarity != POLARITY_REV) {
+		ast_log(LOG_WARNING, "Invalid polarity: '%s'\n", value);
+		return -1;
+	}
+
+	my_set_polarity(pvt, polarity);
+	return 0;
+}
+
+static struct ast_custom_function polarity_function = {
+	.name = "POLARITY",
+	.write = polarity_write,
+	.read = polarity_read,
+};
+
 static int my_start(void *pvt)
 {
 	struct dahdi_pvt *p = pvt;
@@ -3275,7 +3459,7 @@ struct sig_ss7_callback sig_ss7_callbacks =
  */
 static void notify_message(char *mailbox, int thereornot)
 {
-	char s[sizeof(mwimonitornotify) + 80];
+	char s[sizeof(mwimonitornotify) + 164];
 
 	if (ast_strlen_zero(mailbox)) {
 		return;
@@ -3410,7 +3594,7 @@ struct analog_callback analog_callbacks =
 };
 
 /*! Round robin search locations. */
-static struct dahdi_pvt *round_robin[32];
+static struct dahdi_pvt *round_robin[64]; /* groups can range from 0-63 */
 
 int _dahdi_get_index(struct ast_channel *ast, struct dahdi_pvt *p, int nullok, const char *fname, unsigned long line)
 {
@@ -3943,7 +4127,7 @@ static void dahdi_r2_on_context_log(openr2_context_t *r2context, openr2_log_leve
 {
 #define CONTEXT_TAG "Context - "
 	char logmsg[256];
-	char completemsg[sizeof(logmsg) + sizeof(CONTEXT_TAG) - 1];
+	char completemsg[sizeof(logmsg) * 2];
 	vsnprintf(logmsg, sizeof(logmsg), fmt, ap);
 	snprintf(completemsg, sizeof(completemsg), CONTEXT_TAG "%s", logmsg);
 	dahdi_r2_write_log(level, completemsg);
@@ -3956,10 +4140,11 @@ static void dahdi_r2_on_chan_log(openr2_chan_t *r2chan, openr2_log_level_t level
 {
 #define CHAN_TAG "Chan "
 	char logmsg[256];
-	char completemsg[sizeof(logmsg) + sizeof(CHAN_TAG) - 1];
+	char completemsg[sizeof(logmsg) * 2];
 	vsnprintf(logmsg, sizeof(logmsg), fmt, ap);
 	snprintf(completemsg, sizeof(completemsg), CHAN_TAG "%d - %s", openr2_chan_get_number(r2chan), logmsg);
 	dahdi_r2_write_log(level, completemsg);
+#undef CHAN_TAG
 }
 
 static int dahdi_r2_on_dnis_digit_received(openr2_chan_t *r2chan, char digit)
@@ -5974,7 +6159,9 @@ static int dahdi_hangup(struct ast_channel *ast)
 
 	ast_mutex_lock(&p->lock);
 	p->exten[0] = '\0';
-	if (dahdi_analog_lib_handles(p->sig, p->radio, p->oprmode)) {
+	/* Always use sig_analog hangup handling for operator mode */
+	if (dahdi_analog_lib_handles(p->sig, p->radio, 0)) {
+		p->oprmode = 0;
 		dahdi_confmute(p, 0);
 		restore_gains(p);
 		p->ignoredtmf = 0;
@@ -6834,6 +7021,32 @@ static int dahdi_func_read(struct ast_channel *chan, const char *function, char
 		}
 		ast_mutex_unlock(&p->lock);
 #endif	/* defined(HAVE_PRI) */
+	} else if (!strcasecmp(data, "dialmode")) {
+		struct analog_pvt *analog_p;
+		ast_mutex_lock(&p->lock);
+		analog_p = p->sig_pvt;
+		/* Hardcode p->radio and p->oprmode as 0 since we're using this to check for analogness, not the handler */
+		if (dahdi_analog_lib_handles(p->sig, 0, 0) && analog_p) {
+			switch (analog_p->dialmode) {
+			case ANALOG_DIALMODE_BOTH:
+				ast_copy_string(buf, "both", len);
+				break;
+			case ANALOG_DIALMODE_PULSE:
+				ast_copy_string(buf, "pulse", len);
+				break;
+			case ANALOG_DIALMODE_DTMF:
+				ast_copy_string(buf, "dtmf", len);
+				break;
+			case ANALOG_DIALMODE_NONE:
+				ast_copy_string(buf, "none", len);
+				break;
+			}
+		} else {
+			ast_log(LOG_WARNING, "%s only supported on analog channels\n", data);
+			*buf = '\0';
+			res = -1;
+		}
+		ast_mutex_unlock(&p->lock);
 	} else {
 		*buf = '\0';
 		res = -1;
@@ -6939,6 +7152,30 @@ static int dahdi_func_write(struct ast_channel *chan, const char *function, char
 			ast_log(LOG_WARNING, "Unsupported value '%s' provided for '%s' item.\n", value, data);
 			res = -1;
 		}
+	} else if (!strcasecmp(data, "dialmode")) {
+		struct analog_pvt *analog_p;
+
+		ast_mutex_lock(&p->lock);
+		analog_p = p->sig_pvt;
+		if (!dahdi_analog_lib_handles(p->sig, 0, 0) || !analog_p) {
+			ast_log(LOG_WARNING, "%s only supported on analog channels\n", data);
+			ast_mutex_unlock(&p->lock);
+			return -1;
+		}
+		/* analog pvt is used for pulse dialing, so update both */
+		if (!strcasecmp(value, "pulse")) {
+			p->dialmode = analog_p->dialmode = ANALOG_DIALMODE_PULSE;
+		} else if (!strcasecmp(value, "dtmf") || !strcasecmp(value, "tone")) {
+			p->dialmode = analog_p->dialmode = ANALOG_DIALMODE_DTMF;
+		} else if (!strcasecmp(value, "none")) {
+			p->dialmode = analog_p->dialmode = ANALOG_DIALMODE_NONE;
+		} else if (!strcasecmp(value, "both")) {
+			p->dialmode = analog_p->dialmode = ANALOG_DIALMODE_BOTH;
+		} else {
+			ast_log(LOG_WARNING, "'%s' is an invalid setting for %s\n", value, data);
+			res = -1;
+		}
+		ast_mutex_unlock(&p->lock);
 	} else {
 		res = -1;
 	}
@@ -7590,7 +7827,11 @@ static struct ast_frame *dahdi_handle_event(struct ast_channel *ast)
 		}
 		if (p->oprmode < 0)
 		{
-			if (p->oprmode != -1) break;
+			if (p->oprmode != -1) { /* Operator flash recall */
+				ast_verb(4, "Operator mode enabled on channel %d, holding line for channel %d\n", p->channel, p->oprpeer->channel);
+				break;
+			}
+			/* Otherwise, immediate recall */
 			if ((p->sig == SIG_FXOLS) || (p->sig == SIG_FXOKS) || (p->sig == SIG_FXOGS))
 			{
 				/* Make sure it starts ringing */
@@ -7598,6 +7839,7 @@ static struct ast_frame *dahdi_handle_event(struct ast_channel *ast)
 				dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_RING);
 				save_conference(p->oprpeer);
 				tone_zone_play_tone(p->oprpeer->subs[SUB_REAL].dfd, DAHDI_TONE_RINGTONE);
+				ast_verb(4, "Operator recall, channel %d ringing back channel %d\n", p->oprpeer->channel, p->channel);
 			}
 			break;
 		}
@@ -7710,6 +7952,7 @@ static struct ast_frame *dahdi_handle_event(struct ast_channel *ast)
 				dahdi_set_hook(p->subs[SUB_REAL].dfd, DAHDI_RINGOFF);
 				tone_zone_play_tone(p->oprpeer->subs[SUB_REAL].dfd, -1);
 				restore_conference(p->oprpeer);
+				ast_debug(1, "Operator recall by channel %d for channel %d complete\n", p->oprpeer->channel, p->channel);
 			}
 			break;
 		}
@@ -7923,6 +8166,7 @@ static struct ast_frame *dahdi_handle_event(struct ast_channel *ast)
 					dahdi_set_hook(p->oprpeer->subs[SUB_REAL].dfd, DAHDI_RING);
 					save_conference(p);
 					tone_zone_play_tone(p->subs[SUB_REAL].dfd, DAHDI_TONE_RINGTONE);
+					ast_verb(4, "Operator flash recall, channel %d ringing back channel %d\n", p->oprpeer->channel, p->channel);
 				}
 			}
 			break;
@@ -8815,6 +9059,13 @@ static struct ast_frame *dahdi_read(struct ast_channel *ast)
 			} else {
 				dahdi_handle_dtmf(ast, idx, &f);
 			}
+			if (!(p->dialmode == ANALOG_DIALMODE_BOTH || p->dialmode == ANALOG_DIALMODE_DTMF)) {
+				if (f->frametype == AST_FRAME_DTMF_END) { /* only show this message when the key is let go of */
+					ast_debug(1, "Dropping DTMF digit '%c' because tone dialing is disabled\n", f->subclass.integer);
+				}
+				f->frametype = AST_FRAME_NULL;
+				f->subclass.integer = 0;
+			}
 			break;
 		case AST_FRAME_VOICE:
 			if (p->cidspill || p->cid_suppress_expire) {
@@ -11573,6 +11824,7 @@ static void *do_monitor(void *data)
 							&& !analog_p->fxsoffhookstate
 							&& !last->owner
 							&& !ast_strlen_zero(last->mailbox)
+							&& !analog_p->subs[SUB_REAL].owner /* could be a recall ring from a flash hook hold */
 							&& (thispass - analog_p->onhooktime > 3)) {
 							res = has_voicemail(last);
 							if (analog_p->msgstate != res) {
@@ -12597,6 +12849,7 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf,
 #endif
 		tmp->immediate = conf->chan.immediate;
 		tmp->transfertobusy = conf->chan.transfertobusy;
+		tmp->dialmode = conf->chan.dialmode;
 		if (chan_sig & __DAHDI_SIG_FXS) {
 			tmp->mwimonitor_fsk = conf->chan.mwimonitor_fsk;
 			tmp->mwimonitor_neon = conf->chan.mwimonitor_neon;
@@ -12930,6 +13183,7 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf,
 				analog_p->threewaycalling = conf->chan.threewaycalling;
 				analog_p->transfer = conf->chan.transfer;
 				analog_p->transfertobusy = conf->chan.transfertobusy;
+				analog_p->dialmode = conf->chan.dialmode;
 				analog_p->use_callerid = tmp->use_callerid;
 				analog_p->usedistinctiveringdetection = tmp->usedistinctiveringdetection;
 				analog_p->use_smdi = tmp->use_smdi;
@@ -13469,6 +13723,7 @@ static struct ast_channel *dahdi_request(const char *type, struct ast_format_cap
 	struct ast_channel *tmp = NULL;
 	struct dahdi_pvt *exitpvt;
 	int channelmatched = 0;
+	int foundowner = 0;
 	int groupmatched = 0;
 #if defined(HAVE_PRI) || defined(HAVE_SS7)
 	int transcapdigital = 0;
@@ -13492,6 +13747,10 @@ static struct ast_channel *dahdi_request(const char *type, struct ast_format_cap
 		if (start.roundrobin)
 			round_robin[start.rr_starting_point] = p;
 
+		if (p->owner) {
+			foundowner++;
+		}
+
 		if (is_group_or_channel_match(p, start.span, start.groupmatch, &groupmatched, start.channelmatch, &channelmatched)
 			&& available(&p, channelmatched)) {
 			ast_debug(1, "Using channel %d\n", p->channel);
@@ -13610,7 +13869,7 @@ next:
 	ast_mutex_unlock(&iflock);
 	restart_monitor();
 	if (cause && !tmp) {
-		if (callwait || channelmatched) {
+		if (callwait || (channelmatched && foundowner)) {
 			*cause = AST_CAUSE_BUSY;
 		} else if (groupmatched) {
 			*cause = AST_CAUSE_CONGESTION;
@@ -17542,6 +17801,8 @@ static int __unload_module(void)
 	ast_unregister_application(dahdi_accept_r2_call_app);
 #endif
 
+	ast_custom_function_unregister(&polarity_function);
+
 	ast_cli_unregister_multiple(dahdi_cli, ARRAY_LEN(dahdi_cli));
 	ast_manager_unregister("DAHDIDialOffhook");
 	ast_manager_unregister("DAHDIHangup");
@@ -17873,6 +18134,9 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct
 	int y;
 	struct ast_variable *dahdichan = NULL;
 
+	/* Re-parse any cadences from beginning, rather than appending until we run out of room */
+	user_has_defined_cadences = 0;
+
 	for (; v; v = v->next) {
 		if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
 			continue;
@@ -18121,6 +18385,16 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct
 			confp->chan.immediate = ast_true(v->value);
 		} else if (!strcasecmp(v->name, "transfertobusy")) {
 			confp->chan.transfertobusy = ast_true(v->value);
+		} else if (!strcasecmp(v->name, "dialmode")) {
+			if (!strcasecmp(v->value, "pulse")) {
+				confp->chan.dialmode = ANALOG_DIALMODE_PULSE;
+			} else if (!strcasecmp(v->value, "dtmf") || !strcasecmp(v->value, "tone")) {
+				confp->chan.dialmode = ANALOG_DIALMODE_DTMF;
+			} else if (!strcasecmp(v->value, "none")) {
+				confp->chan.dialmode = ANALOG_DIALMODE_NONE;
+			} else {
+				confp->chan.dialmode = ANALOG_DIALMODE_BOTH;
+			}
 		} else if (!strcasecmp(v->name, "mwimonitor")) {
 			confp->chan.mwimonitor_neon = 0;
 			confp->chan.mwimonitor_fsk = 0;
@@ -19224,7 +19498,7 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct
 					report_alarms = REPORT_SPAN_ALARMS;
 			 }
 		} else if (!(options & PROC_DAHDI_OPT_NOWARN) )
-			ast_log(LOG_WARNING, "Ignoring any changes to '%s' (on reload) at line %d.\n", v->name, v->lineno);
+			ast_log(LOG_NOTICE, "Ignoring any changes to '%s' (on reload) at line %d.\n", v->name, v->lineno);
 	}
 
 	if (dahdichan) {
@@ -19727,6 +20001,8 @@ static int load_module(void)
 	ast_register_application_xml(dahdi_accept_r2_call_app, dahdi_accept_r2_call_exec);
 #endif
 
+	ast_custom_function_register(&polarity_function);
+
 	ast_cli_register_multiple(dahdi_cli, ARRAY_LEN(dahdi_cli));
 	memset(round_robin, 0, sizeof(round_robin));
 	ast_manager_register_xml("DAHDITransfer", 0, action_transfer);
diff --git a/channels/chan_dahdi.h b/channels/chan_dahdi.h
index de813f21bf653ae56fda64cc7afc0e75c3209327..8f1668752806c54d9f7f59e60cbf6c8938f404b6 100644
--- a/channels/chan_dahdi.h
+++ b/channels/chan_dahdi.h
@@ -146,6 +146,7 @@ struct dahdi_pvt {
 	 * \note Set to a couple of nonzero values but it is only tested like a boolean.
 	 */
 	int radio;
+	int dialmode;					/*!< Dialing Modes Allowed (Pulse/Tone) */
 	int outsigmod;					/*!< Outbound Signalling style (modifier) */
 	int oprmode;					/*!< "Operator Services" mode */
 	struct dahdi_pvt *oprpeer;				/*!< "Operator Services" peer tech_pvt ptr */
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index ecf5074d9cb27b4a0e09b110fc88242910b3ac22..5b3caf03b540d9c9c301ee23534b5843816e8ebe 100644
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -226,6 +226,16 @@
 			</enum>
 		</enumlist>
 	</info>
+	<info name="Dial_Resource" language="en_US" tech="IAX2">
+		<para>The general syntax is:</para>
+		<para><literal>Dial(IAX2/[username[:password]@]peer[:port][/exten[@context]][/options]</literal></para>
+		<para>IAX2 optionally allows modifiers to be specified after the extension.</para>
+		<enumlist>
+			<enum name="a">
+				<para>Request auto answer (supporting equipment/configuration required)</para>
+			</enum>
+		</enumlist>
+	</info>
 	<manager name="IAXpeers" language="en_US">
 		<synopsis>
 			List IAX peers.
@@ -3407,7 +3417,7 @@ static int send_packet(struct iax_frame *f)
 
 	/* Called with iaxsl held */
 	if (iaxdebug) {
-		ast_debug(3, "Sending %u on %d/%d to %s\n", f->ts, callno, iaxs[callno]->peercallno, ast_sockaddr_stringify(&iaxs[callno]->addr));
+		ast_debug(8, "Sending %u on %d/%d to %s\n", f->ts, callno, iaxs[callno]->peercallno, ast_sockaddr_stringify(&iaxs[callno]->addr));
 	}
 	if (f->transfer) {
 		iax_outputframe(f, NULL, 0, &iaxs[callno]->transfer, f->datalen - sizeof(struct ast_iax2_full_hdr));
@@ -4148,9 +4158,19 @@ static void __get_from_jb(const void *p)
 	now.tv_usec += 1000;
 
 	ms = ast_tvdiff_ms(now, pvt->rxcore);
-
-	voicefmt = ast_format_compatibility_bitfield2format(pvt->voiceformat);
-	if (voicefmt && ms >= (next = jb_next(pvt->jb))) {
+	if (ms >= (next = jb_next(pvt->jb))) {
+		voicefmt = ast_format_compatibility_bitfield2format(pvt->voiceformat);
+		if (!voicefmt) {
+			/* pvt->voiceformat won't be set if we haven't received any voice frames yet.
+			 * In this case, fall back to using the format negotiated during call setup,
+			 * so we don't stall the jitterbuffer completely. */
+			voicefmt = ast_format_compatibility_bitfield2format(pvt->peerformat);
+		}
+		if (!voicefmt) {
+			/* Really shouldn't happen, but if it does, should be looked into */
+			ast_log(LOG_WARNING, "No voice format and no peer format available on %s, backlogging frame\n", ast_channel_name(pvt->owner));
+			goto cleanup; /* Don't crash if there's no voice format */
+		}
 		ret = jb_get(pvt->jb, &frame, ms, ast_format_get_default_ms(voicefmt));
 		switch(ret) {
 		case JB_OK:
@@ -4192,6 +4212,7 @@ static void __get_from_jb(const void *p)
 			break;
 		}
 	}
+cleanup:
 	if (pvt)
 		update_jbsched(pvt);
 	ast_mutex_unlock(&iaxsl[callno]);
@@ -6379,6 +6400,22 @@ static void build_rand_pad(unsigned char *buf, ssize_t len)
 	}
 }
 
+static int invalid_key(ast_aes_decrypt_key *ecx)
+{
+#ifdef HAVE_OPENSSL
+	int i;
+	for (i = 0; i < 60; i++) {
+		if (ecx->raw[i]) {
+			return 0; /* stop if we encounter anything non-zero */
+		}
+	}
+	/* if ast_aes_encrypt or ast_aes_decrypt is called, then we'll crash when calling AES_encrypt or AES_decrypt */
+	return -1;
+#else
+	return 0; /* Can't verify, but doesn't matter anyways */
+#endif
+}
+
 static void build_encryption_keys(const unsigned char *digest, struct chan_iax2_pvt *pvt)
 {
 	build_ecx_key(digest, pvt);
@@ -7375,7 +7412,7 @@ static char *handle_cli_iax2_show_registry(struct ast_cli_entry *e, int cmd, str
 	AST_LIST_TRAVERSE(&registrations, reg, entry) {
 		snprintf(host, sizeof(host), "%s", ast_sockaddr_stringify(&reg->addr));
 
-		snprintf(perceived, sizeof(perceived), "%s", ast_sockaddr_isnull(&reg->addr) ? "<Unregistered>" : ast_sockaddr_stringify(&reg->addr));
+		snprintf(perceived, sizeof(perceived), "%s", ast_sockaddr_isnull(&reg->us) ? "<Unregistered>" : ast_sockaddr_stringify(&reg->us));
 
 		ast_cli(a->fd, FORMAT, host,
 				(reg->dnsmgr) ? "Y" : "N",
@@ -7407,7 +7444,7 @@ static int manager_iax2_show_registry(struct mansession *s, const struct message
 	AST_LIST_TRAVERSE(&registrations, reg, entry) {
 		snprintf(host, sizeof(host), "%s", ast_sockaddr_stringify(&reg->addr));
 
-		snprintf(perceived, sizeof(perceived), "%s", ast_sockaddr_isnull(&reg->addr) ? "<Unregistered>" : ast_sockaddr_stringify(&reg->addr));
+		snprintf(perceived, sizeof(perceived), "%s", ast_sockaddr_isnull(&reg->us) ? "<Unregistered>" : ast_sockaddr_stringify(&reg->us));
 
 		astman_append(s,
 			"Event: RegistryEntry\r\n"
@@ -7503,7 +7540,7 @@ static int ast_cli_netstats(struct mansession *s, int fd, int limit_fmt)
 	int numchans = 0;
 	char first_message[10] = { 0, };
 	char last_message[10] = { 0, };
-#define ACN_FORMAT1 "%-20.25s %4u %4d %4d %5d %3d %5d %4d %6d %4d %4d %5d %3d %5d %4d %6d %s%s %4s%s\n"
+#define ACN_FORMAT1 "%-24.25s %4u %4d %4d %5d %3d %5d %4d %6d %4d %4d %5d %3d %5d %4d %6d %s%s %4s%s\n"
 #define ACN_FORMAT2 "%s %u %d %d %d %d %d %d %d %d %d %d %d %d %d %d %s%s %s%s\n"
 	for (x = 0; x < ARRAY_LEN(iaxs); x++) {
 		ast_mutex_lock(&iaxsl[x]);
@@ -7597,8 +7634,8 @@ static char *handle_cli_iax2_show_netstats(struct ast_cli_entry *e, int cmd, str
 	}
 	if (a->argc != 3)
 		return CLI_SHOWUSAGE;
-	ast_cli(a->fd, "                           -------- LOCAL ---------------------  -------- REMOTE --------------------\n");
-	ast_cli(a->fd, "Channel               RTT  Jit  Del  Lost   %%  Drop  OOO  Kpkts  Jit  Del  Lost   %%  Drop  OOO  Kpkts FirstMsg    LastMsg\n");
+	ast_cli(a->fd, "                                -------- LOCAL ---------------------  -------- REMOTE --------------------\n");
+	ast_cli(a->fd, "Channel                    RTT  Jit  Del  Lost   %%  Drop  OOO  Kpkts  Jit  Del  Lost   %%  Drop  OOO  Kpkts FirstMsg    LastMsg\n");
 	numchans = ast_cli_netstats(NULL, a->fd, 1);
 	ast_cli(a->fd, "%d active IAX channel%s\n", numchans, (numchans != 1) ? "s" : "");
 	return CLI_SUCCESS;
@@ -8435,7 +8472,7 @@ static int authenticate(const char *challenge, const char *secret, const char *k
 			iax_ie_append_str(ied, IAX_IE_PASSWORD, secret);
 			res = 0;
 		} else
-			ast_log(LOG_NOTICE, "No way to send secret to peer '%s' (their methods: %d)\n", ast_sockaddr_stringify_addr(addr), authmethods);
+			ast_log(LOG_WARNING, "No way to send secret to peer '%s' (their methods: %d)\n", ast_sockaddr_stringify_addr(addr), authmethods);
 	}
 	return res;
 }
@@ -8520,12 +8557,22 @@ static int authenticate_reply(struct chan_iax2_pvt *p, struct ast_sockaddr *addr
 		}
 	}
 
+	if (!(ies->authmethods & (IAX_AUTH_MD5 | IAX_AUTH_PLAINTEXT)) && (ies->authmethods & IAX_AUTH_RSA) && ast_strlen_zero(okey)) {
+		/* If the only thing available is RSA, and we don't have an outkey, we can't do it... */
+		ast_log(LOG_WARNING, "Call terminated. RSA authentication requires an outkey\n");
+		return -1;
+	}
+
 	if (ies->encmethods) {
 		if (ast_strlen_zero(p->secret) &&
 			((ies->authmethods & IAX_AUTH_RSA) || (ies->authmethods & IAX_AUTH_MD5) || (ies->authmethods & IAX_AUTH_PLAINTEXT))) {
 			ast_log(LOG_WARNING, "Call terminated. Encryption requested by peer but no secret available locally\n");
 			return -1;
 		}
+		/* Don't even THINK about trying to encrypt or decrypt anything if we don't have valid keys, for some reason... */
+		/* If either of these happens, it's our fault, not the user's. But we should abort rather than crash. */
+		ast_assert_return(!invalid_key(&p->ecx), -1);
+		ast_assert_return(!invalid_key(&p->dcx), -1);
 		ast_set_flag64(p, IAX_ENCRYPTED | IAX_KEYPOPULATED);
 	} else if (ast_test_flag64(iaxs[callno], IAX_FORCE_ENCRYPT)) {
 		ast_log(LOG_NOTICE, "Call initiated without encryption while forceencryption=yes option is set\n");
@@ -10337,7 +10384,7 @@ static int socket_process_helper(struct iax2_thread *thread)
 	}
 	if (ast_test_flag64(iaxs[fr->callno], IAX_ENCRYPTED) && !decrypted) {
 		if (decrypt_frame(fr->callno, fh, &f, &res)) {
-			ast_log(LOG_NOTICE, "Packet Decrypt Failed!\n");
+			ast_log(LOG_WARNING, "Packet Decrypt Failed!\n");
 			ast_variables_destroy(ies.vars);
 			ast_mutex_unlock(&iaxsl[fr->callno]);
 			return 1;
@@ -12007,7 +12054,7 @@ immediatedial:
 		iaxs[fr->callno]->last = fr->ts;
 #if 1
 		if (iaxdebug)
-			ast_debug(3, "For call=%d, set last=%u\n", fr->callno, fr->ts);
+			ast_debug(8, "For call=%d, set last=%u\n", fr->callno, fr->ts);
 #endif
 	}
 
@@ -14192,9 +14239,7 @@ static struct iax2_dpcache *find_cache(struct ast_channel *chan, const char *dat
 {
 	struct iax2_dpcache *dp = NULL;
 	struct timeval now = ast_tvnow();
-	int x, com[2], timeout, old = 0, outfd, doabort, callno;
-	struct ast_channel *c = NULL;
-	struct ast_frame *f = NULL;
+	int x, com[2], timeout, doabort, callno;
 
 	AST_LIST_TRAVERSE_SAFE_BEGIN(&dpcache, dp, cache_list) {
 		if (ast_tvcmp(now, dp->expiry) > 0) {
@@ -14241,8 +14286,8 @@ static struct iax2_dpcache *find_cache(struct ast_channel *chan, const char *dat
 
 	/* By here we must have a dp */
 	if (dp->flags & CACHE_FLAG_PENDING) {
-		struct timeval start;
-		int ms;
+		int res;
+		struct pollfd pfd;
 		/* Okay, here it starts to get nasty.  We need a pipe now to wait
 		   for a reply to come back so long as it's pending */
 		for (x = 0; x < ARRAY_LEN(dp->waiters); x++) {
@@ -14263,35 +14308,31 @@ static struct iax2_dpcache *find_cache(struct ast_channel *chan, const char *dat
 		timeout = iaxdefaulttimeout * 1000;
 		/* Temporarily unlock */
 		AST_LIST_UNLOCK(&dpcache);
-		/* Defer any dtmf */
-		if (chan)
-			old = ast_channel_defer_dtmf(chan);
 		doabort = 0;
-		start = ast_tvnow();
-		while ((ms = ast_remaining_ms(start, timeout))) {
-			c = ast_waitfor_nandfds(&chan, chan ? 1 : 0, &com[0], 1, NULL, &outfd, &ms);
-			if (outfd > -1)
-				break;
-			if (!c)
-				continue;
-			if (!(f = ast_read(c))) {
-				doabort = 1;
-				break;
-			}
-			ast_frfree(f);
-		}
-		if (!ms) {
+
+		/* chan is in autoservice here, so do NOT service it here! */
+		pfd.fd = com[0];
+		pfd.events = POLLIN;
+		pfd.revents = 0;
+		/* Wait for pipe activity... if the channel hangs up, we'll catch it on the way out. */
+		res = ast_poll(&pfd, 1, timeout);
+		if (res < 0) {
+			ast_log(LOG_WARNING, "poll returned < 0: %s\n", strerror(errno));
+			return NULL;
+		} else if (!pfd.revents) {
 			ast_log(LOG_WARNING, "Timeout waiting for %s exten %s\n", data, exten);
 		}
+
+		if (ast_check_hangup(chan)) {
+			doabort = 1;
+		}
+
 		AST_LIST_LOCK(&dpcache);
 		dp->waiters[x] = -1;
 		close(com[1]);
 		close(com[0]);
 		if (doabort) {
-			/* Don't interpret anything, just abort.  Not sure what th epoint
-			  of undeferring dtmf on a hung up channel is but hey whatever */
-			if (!old && chan)
-				ast_channel_undefer_dtmf(chan);
+			/* Don't interpret anything, just abort. */
 			return NULL;
 		}
 		if (!(dp->flags & CACHE_FLAG_TIMEOUT)) {
@@ -14314,8 +14355,6 @@ static struct iax2_dpcache *find_cache(struct ast_channel *chan, const char *dat
 			}
 		}
 		/* Our caller will obtain the rest */
-		if (!old && chan)
-			ast_channel_undefer_dtmf(chan);
 	}
 	return dp;
 }
diff --git a/channels/chan_mgcp.c b/channels/chan_mgcp.c
index fb6626a125fedcae1706220b561a38c179276692..b2f1e571b5e9721e44460922026b93a9982d66af 100644
--- a/channels/chan_mgcp.c
+++ b/channels/chan_mgcp.c
@@ -35,7 +35,8 @@
 
 /*** MODULEINFO
         <use type="module">res_pktccops</use>
-	<support_level>extended</support_level>
+	<defaultenabled>no</defaultenabled>
+	<support_level>deprecated</support_level>
 	<deprecated_in>19</deprecated_in>
 	<removed_in>21</removed_in>
  ***/
@@ -4999,7 +5000,7 @@ static int unload_module(void)
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Media Gateway Control Protocol (MGCP)",
-	.support_level = AST_MODULE_SUPPORT_EXTENDED,
+	.support_level = AST_MODULE_SUPPORT_DEPRECATED,
 	.load = load_module,
 	.unload = unload_module,
 	.reload = reload,
diff --git a/channels/chan_misdn.c b/channels/chan_misdn.c
deleted file mode 100644
index c05a5bc5555ad7e8611ac2a72c25c7adf32959b4..0000000000000000000000000000000000000000
--- a/channels/chan_misdn.c
+++ /dev/null
@@ -1,12782 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2004 - 2006, Christian Richter
- *
- * Christian Richter <crich@beronet.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- *
- */
-
-/*!
- * \file
- *
- * \brief the chan_misdn channel driver for Asterisk
- *
- * \author Christian Richter <crich@beronet.com>
- *
- * MISDN http://www.misdn.org/
- *
- * \ingroup channel_drivers
- */
-
-/*! \li \ref chan_misdn.c uses the configuration file \ref misdn.conf
- * \addtogroup configuration_file
- */
-
-/*! \page misdn.conf misdn.conf
- * \verbinclude misdn.conf.sample
- */
-
-/*!
- * \note
- * To use the CCBS/CCNR supplementary service feature and other
- * supplementary services using FACILITY messages requires a
- * modified version of mISDN.
- *
- * \note
- * The latest modified mISDN v1.1.x based version is available at:
- * http://svn.digium.com/svn/thirdparty/mISDN/trunk
- * http://svn.digium.com/svn/thirdparty/mISDNuser/trunk
- *
- * \note
- * Taged versions of the modified mISDN code are available under:
- * http://svn.digium.com/svn/thirdparty/mISDN/tags
- * http://svn.digium.com/svn/thirdparty/mISDNuser/tags
- */
-
-/* Define to enable cli commands to generate canned CCBS messages. */
-// #define CCBS_TEST_MESSAGES	1
-
-/*
- * XXX The mISDN channel driver needs its native bridge code
- * converted to the new bridge technology scheme.  The
- * chan_dahdi native bridge code can be used as an example.  It
- * is unlikely that this will ever get done.  Support for this
- * channel driver is dwindling because the supported version of
- * mISDN does not support newer kernels.
- *
- * Without native bridge support, the following config file
- * parameters have no effect: bridging.
- *
- * The existing native bridge code is marked with the
- * mISDN_NATIVE_BRIDGING conditional.
- */
-
-/*** MODULEINFO
-	<depend>isdnnet</depend>
-	<depend>misdn</depend>
-	<depend>suppserv</depend>
-	<support_level>deprecated</support_level>
-	<replacement>chan_dahdi</replacement>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include "asterisk.h"
-
-#include <pthread.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <arpa/inet.h>
-#include <fcntl.h>
-#include <sys/ioctl.h>
-#include <signal.h>
-#include <sys/file.h>
-#include <semaphore.h>
-#include <ctype.h>
-#include <time.h>
-
-#include "asterisk/channel.h"
-#include "asterisk/config.h"
-#include "asterisk/module.h"
-#include "asterisk/pbx.h"
-#include "asterisk/io.h"
-#include "asterisk/frame.h"
-#include "asterisk/translate.h"
-#include "asterisk/cli.h"
-#include "asterisk/musiconhold.h"
-#include "asterisk/dsp.h"
-#include "asterisk/file.h"
-#include "asterisk/callerid.h"
-#include "asterisk/indications.h"
-#include "asterisk/app.h"
-#include "asterisk/features.h"
-#include "asterisk/term.h"
-#include "asterisk/sched.h"
-#include "asterisk/stringfields.h"
-#include "asterisk/abstract_jb.h"
-#include "asterisk/causes.h"
-#include "asterisk/format.h"
-#include "asterisk/format_cap.h"
-#include "asterisk/features_config.h"
-#include "asterisk/bridge.h"
-#include "asterisk/pickup.h"
-#include "asterisk/format_cache.h"
-
-#include "chan_misdn_config.h"
-#include "isdn_lib.h"
-
-static char global_tracefile[BUFFERSIZE + 1];
-
-static int g_config_initialized = 0;
-
-struct misdn_jb{
-	int size;
-	int upper_threshold;
-	char *samples, *ok;
-	int wp,rp;
-	int state_empty;
-	int state_full;
-	int state_buffer;
-	int bytes_wrote;
-	ast_mutex_t mutexjb;
-};
-
-/*! \brief allocates the jb-structure and initialize the elements */
-struct misdn_jb *misdn_jb_init(int size, int upper_threshold);
-
-/*! \brief frees the data and destroys the given jitterbuffer struct */
-void misdn_jb_destroy(struct misdn_jb *jb);
-
-/*! \brief fills the jitterbuffer with len data returns < 0 if there was an
-error (buffer overrun). */
-int misdn_jb_fill(struct misdn_jb *jb, const char *data, int len);
-
-/*! \brief gets len bytes out of the jitterbuffer if available, else only the
-available data is returned and the return value indicates the number
-of data. */
-int misdn_jb_empty(struct misdn_jb *jb, char *data, int len);
-
-static char *complete_ch(struct ast_cli_args *a);
-static char *complete_debug_port(struct ast_cli_args *a);
-static char *complete_show_config(struct ast_cli_args *a);
-
-/* BEGIN: chan_misdn.h */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*
- * This timeout duration is to clean up any call completion records that
- * are forgotten about by the switch.
- */
-#define MISDN_CC_RECORD_AGE_MAX		(6UL * 60 * 60)	/* seconds */
-
-#define MISDN_CC_REQUEST_WAIT_MAX	5	/* seconds */
-
-/*!
- * \brief Caller that initialized call completion services
- *
- * \details
- * This data is the payload for a datastore that is put on the channel that
- * initializes call completion services.  This datastore is set to be inherited
- * by the outbound mISDN channel.  When one of these channels hangs up, the
- * channel pointer will be set to NULL.  That way, we can ensure that we do not
- * touch this channel after it gets destroyed.
- */
-struct misdn_cc_caller {
-	/*! \brief The channel that initialized call completion services */
-	struct ast_channel *chan;
-};
-
-struct misdn_cc_notify {
-	/*! \brief Dialplan: Notify extension priority */
-	int priority;
-
-	/*! \brief Dialplan: Notify extension context */
-	char context[AST_MAX_CONTEXT];
-
-	/*! \brief Dialplan: Notify extension number (User-A) */
-	char exten[AST_MAX_EXTENSION];
-};
-
-/*! \brief mISDN call completion record */
-struct misdn_cc_record {
-	/*! \brief Call completion record linked list */
-	AST_LIST_ENTRY(misdn_cc_record) list;
-
-	/*! \brief Time the record was created. */
-	time_t time_created;
-
-	/*! \brief MISDN_CC_RECORD_ID value */
-	long record_id;
-
-	/*!
-	 * \brief Logical Layer 1 port associated with this
-	 * call completion record
-	 */
-	int port;
-
-	/*! \brief TRUE if point-to-point mode (CCBS-T/CCNR-T mode) */
-	int ptp;
-
-	/*! \brief Mode specific parameters */
-	union {
-		/*! \brief point-to-point specific parameters. */
-		struct {
-			/*!
-			 * \brief Call-completion signaling link.
-			 * NULL if signaling link not established.
-			 */
-			struct misdn_bchannel *bc;
-
-			/*!
-			 * \brief TRUE if we requested the request retention option
-			 * to be enabled.
-			 */
-			int requested_retention;
-
-			/*!
-			 * \brief TRUE if the request retention option is enabled.
-			 */
-			int retention_enabled;
-		} ptp;
-
-		/*! \brief point-to-multi-point specific parameters. */
-		struct {
-			/*! \brief CallLinkageID (valid when port determined) */
-			int linkage_id;
-
-			/*! \brief CCBSReference (valid when activated is TRUE) */
-			int reference_id;
-
-			/*! \brief globalRecall(0),	specificRecall(1) */
-			int recall_mode;
-		} ptmp;
-	} mode;
-
-	/*! \brief TRUE if call completion activated */
-	int activated;
-
-	/*! \brief Outstanding message ID (valid when outstanding_message) */
-	int invoke_id;
-
-	/*! \brief TRUE if waiting for a response from a message (invoke_id is valid) */
-	int outstanding_message;
-
-	/*! \brief TRUE if activation has been requested */
-	int activation_requested;
-
-	/*!
-	 * \brief TRUE if User-A is free
-	 * \note PTMP - Used to answer CCBSStatusRequest.
-	 * PTP - Determines how to respond to CCBS_T_RemoteUserFree.
-	 */
-	int party_a_free;
-
-	/*! \brief Error code received from last outstanding message. */
-	enum FacErrorCode error_code;
-
-	/*! \brief Reject code received from last outstanding message. */
-	enum FacRejectCode reject_code;
-
-	/*!
-	 * \brief Saved struct misdn_bchannel call information when
-	 * attempted to call User-B
-	 */
-	struct {
-		/*! \brief User-A caller id information */
-		struct misdn_party_id caller;
-
-		/*! \brief User-B number information */
-		struct misdn_party_dialing dialed;
-
-		/*! \brief The BC, HLC (optional) and LLC (optional) contents from the SETUP message. */
-		struct Q931_Bc_Hlc_Llc setup_bc_hlc_llc;
-
-		/*! \brief SETUP message bearer capability field code value */
-		int capability;
-
-		/*! \brief TRUE if call made in digital HDLC mode */
-		int hdlc;
-	} redial;
-
-	/*! \brief Dialplan location to indicate User-B free and User-A is free */
-	struct misdn_cc_notify remote_user_free;
-
-	/*! \brief Dialplan location to indicate User-B free and User-A is busy */
-	struct misdn_cc_notify b_free;
-};
-
-/*! \brief mISDN call completion record database */
-static AST_LIST_HEAD_STATIC(misdn_cc_records_db, misdn_cc_record);
-/*! \brief Next call completion record ID to use */
-static __u16 misdn_cc_record_id;
-/*! \brief Next invoke ID to use */
-static __s16 misdn_invoke_id;
-
-static const char misdn_no_response_from_network[] = "No response from network";
-static const char misdn_cc_record_not_found[] = "Call completion record not found";
-
-/* mISDN channel variable names */
-#define MISDN_CC_RECORD_ID	"MISDN_CC_RECORD_ID"
-#define MISDN_CC_STATUS		"MISDN_CC_STATUS"
-#define MISDN_ERROR_MSG		"MISDN_ERROR_MSG"
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-static ast_mutex_t release_lock;
-
-enum misdn_chan_state {
-	MISDN_NOTHING = 0,         /*!< at beginning */
-	MISDN_WAITING4DIGS,        /*!< when waiting for info */
-	MISDN_EXTCANTMATCH,        /*!< when asterisk couldn't match our ext */
-	MISDN_INCOMING_SETUP,      /*!< for incoming setup */
-	MISDN_DIALING,             /*!< when pbx_start */
-	MISDN_PROGRESS,            /*!< we have progress */
-	MISDN_PROCEEDING,          /*!< we have progress */
-	MISDN_CALLING,             /*!< when misdn_call is called */
-	MISDN_CALLING_ACKNOWLEDGE, /*!< when we get SETUP_ACK */
-	MISDN_ALERTING,            /*!< when Alerting */
-	MISDN_BUSY,                /*!< when BUSY */
-	MISDN_CONNECTED,           /*!< when connected */
-	MISDN_DISCONNECTED,        /*!< when connected */
-	MISDN_CLEANING,            /*!< when hangup from * but we were connected before */
-};
-
-/*! Asterisk created the channel (outgoing call) */
-#define ORG_AST 1
-/*! mISDN created the channel (incoming call) */
-#define ORG_MISDN 2
-
-enum misdn_hold_state {
-	MISDN_HOLD_IDLE,		/*!< HOLD not active */
-	MISDN_HOLD_ACTIVE,		/*!< Call is held */
-	MISDN_HOLD_TRANSFER,	/*!< Held call is being transferred */
-	MISDN_HOLD_DISCONNECT,	/*!< Held call is being disconnected */
-};
-struct hold_info {
-	/*!
-	 * \brief Call HOLD state.
-	 */
-	enum misdn_hold_state state;
-	/*!
-	 * \brief Logical port the channel call record is HELD on
-	 * because the B channel is no longer associated.
-	 */
-	int port;
-
-	/*!
-	 * \brief Original B channel number the HELD call was using.
-	 * \note Used only for debug display messages.
-	 */
-	int channel;
-};
-
-#define chan_list_ref(obj, debug) ao2_t_ref((obj), +1, (debug))
-#define chan_list_unref(obj, debug) ao2_t_ref((obj), -1, (debug))
-
-/*!
- * \brief Channel call record structure
- */
-struct chan_list {
-	/*!
-	 * \brief The "allowed_bearers" string read in from /etc/asterisk/misdn.conf
-	 */
-	char allowed_bearers[BUFFERSIZE + 1];
-
-	/*!
-	 * \brief State of the channel
-	 */
-	enum misdn_chan_state state;
-
-	/*!
-	 * \brief TRUE if a hangup needs to be queued
-	 * \note This is a debug flag only used to catch calls to hangup_chan() that are already hungup.
-	 */
-	int need_queue_hangup;
-
-	/*!
-	 * \brief TRUE if a channel can be hung up by calling asterisk directly when done.
-	 */
-	int need_hangup;
-
-	/*!
-	 * \brief TRUE if we could send an AST_CONTROL_BUSY if needed.
-	 */
-	int need_busy;
-
-	/*!
-	 * \brief Who originally created this channel. ORG_AST or ORG_MISDN
-	 */
-	int originator;
-
-	/*!
-	 * \brief TRUE of we are not to respond immediately to a SETUP message.  Check the dialplan first.
-	 * \note The "noautorespond_on_setup" boolean read in from /etc/asterisk/misdn.conf
-	 */
-	int noautorespond_on_setup;
-
-	int norxtone;	/*!< Boolean assigned values but the value is not used. */
-
-	/*!
-	 * \brief TRUE if we are not to generate tones (Playtones)
-	 */
-	int notxtone;
-
-	/*!
-	 * \brief TRUE if echo canceller is enabled.  Value is toggled.
-	 */
-	int toggle_ec;
-
-	/*!
-	 * \brief TRUE if you want to send Tone Indications to an incoming
-	 * ISDN channel on a TE Port.
-	 * \note The "incoming_early_audio" boolean read in from /etc/asterisk/misdn.conf
-	 */
-	int incoming_early_audio;
-
-	/*!
-	 * \brief TRUE if DTMF digits are to be passed inband only.
-	 * \note It is settable by the misdn_set_opt() application.
-	 */
-	int ignore_dtmf;
-
-	/*!
-	 * \brief Pipe file descriptor handles array.
-	 * Read from pipe[0], write to pipe[1]
-	 */
-	int pipe[2];
-
-	/*!
-	 * \brief Read buffer for inbound audio from pipe[0]
-	 */
-	char ast_rd_buf[4096];
-
-	/*!
-	 * \brief Inbound audio frame returned by misdn_read().
-	 */
-	struct ast_frame frame;
-
-	/*!
-	 * \brief Fax detection option. (0:no 1:yes 2:yes+nojump)
-	 * \note The "faxdetect" option string read in from /etc/asterisk/misdn.conf
-	 * \note It is settable by the misdn_set_opt() application.
-	 */
-	int faxdetect;
-
-	/*!
-	 * \brief Number of seconds to detect a Fax machine when detection enabled.
-	 * \note 0 disables the timeout.
-	 * \note The "faxdetect_timeout" value read in from /etc/asterisk/misdn.conf
-	 */
-	int faxdetect_timeout;
-
-	/*!
-	 * \brief Starting time of fax detection with timeout when nonzero.
-	 */
-	struct timeval faxdetect_tv;
-
-	/*!
-	 * \brief TRUE if a fax has been detected.
-	 */
-	int faxhandled;
-
-	/*!
-	 * \brief TRUE if we will use the Asterisk DSP to detect DTMF/Fax
-	 * \note The "astdtmf" boolean read in from /etc/asterisk/misdn.conf
-	 */
-	int ast_dsp;
-
-	/*!
-	 * \brief Jitterbuffer length
-	 * \note The "jitterbuffer" value read in from /etc/asterisk/misdn.conf
-	 */
-	int jb_len;
-
-	/*!
-	 * \brief Jitterbuffer upper threshold
-	 * \note The "jitterbuffer_upper_threshold" value read in from /etc/asterisk/misdn.conf
-	 */
-	int jb_upper_threshold;
-
-	/*!
-	 * \brief Allocated jitterbuffer controller
-	 * \note misdn_jb_init() creates the jitterbuffer.
-	 * \note Must use misdn_jb_destroy() to clean up.
-	 */
-	struct misdn_jb *jb;
-
-	/*!
-	 * \brief Allocated DSP controller
-	 * \note ast_dsp_new() creates the DSP controller.
-	 * \note Must use ast_dsp_free() to clean up.
-	 */
-	struct ast_dsp *dsp;
-
-	/*!
-	 * \brief Associated Asterisk channel structure.
-	 */
-	struct ast_channel * ast;
-
-	/*!
-	 * \brief Associated B channel structure.
-	 */
-	struct misdn_bchannel *bc;
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	/*!
-	 * \brief Peer channel for which call completion was initialized.
-	 */
-	struct misdn_cc_caller *peer;
-
-	/*! \brief Associated call completion record ID (-1 if not associated) */
-	long record_id;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-	/*!
-	 * \brief HELD channel call information
-	 */
-	struct hold_info hold;
-
-	/*!
-	 * \brief From associated B channel: Layer 3 process ID
-	 * \note Used to find the HELD channel call record when retrieving a call.
-	 */
-	unsigned int l3id;
-
-	/*!
-	 * \brief From associated B channel: B Channel mISDN driver layer ID from mISDN_get_layerid()
-	 * \note Used only for debug display messages.
-	 */
-	int addr;
-
-	/*!
-	 * \brief Incoming call dialplan context identifier.
-	 * \note The "context" string read in from /etc/asterisk/misdn.conf
-	 */
-	char context[AST_MAX_CONTEXT];
-
-	/*!
-	 * \brief The configured music-on-hold class to use for this call.
-	 * \note The "musicclass" string read in from /etc/asterisk/misdn.conf
-	 */
-	char mohinterpret[MAX_MUSICCLASS];
-
-	/*!
-	 * \brief Number of outgoing audio frames dropped since last debug gripe message.
-	 */
-	int dropped_frame_cnt;
-
-	/*!
-	 * \brief TRUE if we must do the ringback tones.
-	 * \note The "far_alerting" boolean read in from /etc/asterisk/misdn.conf
-	 */
-	int far_alerting;
-
-	/*!
-	 * \brief TRUE if NT should disconnect an overlap dialing call when a timeout occurs.
-	 * \note The "nttimeout" boolean read in from /etc/asterisk/misdn.conf
-	 */
-	int nttimeout;
-
-	/*!
-	 * \brief Tone zone sound used for dialtone generation.
-	 * \note Used as a boolean.  Non-NULL to prod generation if enabled.
-	 */
-	struct ast_tone_zone_sound *ts;
-
-	/*!
-	 * \brief Enables overlap dialing for the set amount of seconds.  (0 = Disabled)
-	 * \note The "overlapdial" value read in from /etc/asterisk/misdn.conf
-	 */
-	int overlap_dial;
-
-	/*!
-	 * \brief Overlap dialing timeout Task ID.  -1 if not running.
-	 */
-	int overlap_dial_task;
-
-	/*!
-	 * \brief overlap_tv access lock.
-	 */
-	ast_mutex_t overlap_tv_lock;
-
-	/*!
-	 * \brief Overlap timer start time.  Timer restarted for every digit received.
-	 */
-	struct timeval overlap_tv;
-
-	/*!
-	 * \brief Next channel call record in the list.
-	 */
-	struct chan_list *next;
-};
-
-
-int MAXTICS = 8;
-
-
-void export_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch);
-void import_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch);
-static struct ast_frame *process_ast_dsp(struct chan_list *tmp, struct ast_frame *frame);
-
-struct robin_list {
-	char *group;
-	int port;
-	int channel;
-	struct robin_list *next;
-	struct robin_list *prev;
-};
-static struct robin_list *robin = NULL;
-
-
-static void free_robin_list(void)
-{
-	struct robin_list *r;
-	struct robin_list *next;
-
-	for (r = robin, robin = NULL; r; r = next) {
-		next = r->next;
-		ast_free(r->group);
-		ast_free(r);
-	}
-}
-
-static struct robin_list *get_robin_position(char *group)
-{
-	struct robin_list *new;
-	struct robin_list *iter = robin;
-	for (; iter; iter = iter->next) {
-		if (!strcasecmp(iter->group, group)) {
-			return iter;
-		}
-	}
-	new = ast_calloc(1, sizeof(*new));
-	if (!new) {
-		return NULL;
-	}
-	new->group = ast_strdup(group);
-	if (!new->group) {
-		ast_free(new);
-		return NULL;
-	}
-	new->channel = 1;
-	if (robin) {
-		new->next = robin;
-		robin->prev = new;
-	}
-	robin = new;
-	return robin;
-}
-
-
-/*! \brief the main schedule context for stuff like l1 watcher, overlap dial, ... */
-static struct ast_sched_context *misdn_tasks = NULL;
-static pthread_t misdn_tasks_thread;
-
-static int *misdn_ports;
-
-static void chan_misdn_log(int level, int port, char *tmpl, ...)
-	__attribute__((format(printf, 3, 4)));
-
-static struct ast_channel *misdn_new(struct chan_list *cl, int state,  char *exten, char *callerid, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, int port, int c);
-static void send_digit_to_chan(struct chan_list *cl, char digit);
-
-static int pbx_start_chan(struct chan_list *ch);
-
-#define MISDN_ASTERISK_TECH_PVT(ast) ast_channel_tech_pvt(ast)
-#define MISDN_ASTERISK_TECH_PVT_SET(ast, value) ast_channel_tech_pvt_set(ast, value)
-
-#include "asterisk/strings.h"
-
-/* #define MISDN_DEBUG 1 */
-
-static const char misdn_type[] = "mISDN";
-
-static int tracing = 0;
-
-static int *misdn_debug;
-static int *misdn_debug_only;
-static int max_ports;
-
-static int *misdn_in_calls;
-static int *misdn_out_calls;
-
-/*!
- * \brief Global channel call record list head.
- */
-static struct chan_list *cl_te=NULL;
-static ast_mutex_t cl_te_lock;
-
-static enum event_response_e
-cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data);
-
-static int send_cause2ast(struct ast_channel *ast, struct misdn_bchannel *bc, struct chan_list *ch);
-
-static void cl_queue_chan(struct chan_list *chan);
-
-static int dialtone_indicate(struct chan_list *cl);
-static void hanguptone_indicate(struct chan_list *cl);
-static int stop_indicate(struct chan_list *cl);
-
-static int start_bc_tones(struct chan_list *cl);
-static int stop_bc_tones(struct chan_list *cl);
-static void release_chan_early(struct chan_list *ch);
-static void release_chan(struct chan_list *ch, struct misdn_bchannel *bc);
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static const char misdn_command_name[] = "misdn_command";
-static int misdn_command_exec(struct ast_channel *chan, const char *data);
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-static int misdn_check_l2l1(struct ast_channel *chan, const char *data);
-static int misdn_set_opt_exec(struct ast_channel *chan, const char *data);
-static int misdn_facility_exec(struct ast_channel *chan, const char *data);
-
-int chan_misdn_jb_empty(struct misdn_bchannel *bc, char *buf, int len);
-
-void debug_numtype(int port, int numtype, char *type);
-
-int add_out_calls(int port);
-int add_in_calls(int port);
-
-
-#ifdef MISDN_1_2
-static int update_pipeline_config(struct misdn_bchannel *bc);
-#else
-static int update_ec_config(struct misdn_bchannel *bc);
-#endif
-
-
-
-/*************** Helpers *****************/
-
-static int misdn_chan_is_valid(struct chan_list *ch)
-{
-	struct chan_list *list;
-
-	ast_mutex_lock(&cl_te_lock);
-	for (list = cl_te; list; list = list->next) {
-		if (list == ch) {
-			ast_mutex_unlock(&cl_te_lock);
-			return 1;
-		}
-	}
-	ast_mutex_unlock(&cl_te_lock);
-
-	return 0;
-}
-
-#if defined(mISDN_NATIVE_BRIDGING)
-/*! Returns a reference to the found chan_list. */
-static struct chan_list *get_chan_by_ast(struct ast_channel *ast)
-{
-	struct chan_list *tmp;
-
-	ast_mutex_lock(&cl_te_lock);
-	for (tmp = cl_te; tmp; tmp = tmp->next) {
-		if (tmp->ast == ast) {
-			chan_list_ref(tmp, "Found chan_list by ast");
-			ast_mutex_unlock(&cl_te_lock);
-			return tmp;
-		}
-	}
-	ast_mutex_unlock(&cl_te_lock);
-
-	return NULL;
-}
-#endif	/* defined(mISDN_NATIVE_BRIDGING) */
-
-/*! Returns a reference to the found chan_list. */
-static struct chan_list *get_chan_by_ast_name(const char *name)
-{
-	struct chan_list *tmp;
-
-	ast_mutex_lock(&cl_te_lock);
-	for (tmp = cl_te; tmp; tmp = tmp->next) {
-		if (tmp->ast && strcmp(ast_channel_name(tmp->ast), name) == 0) {
-			chan_list_ref(tmp, "Found chan_list by ast name");
-			ast_mutex_unlock(&cl_te_lock);
-			return tmp;
-		}
-	}
-	ast_mutex_unlock(&cl_te_lock);
-
-	return NULL;
-}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Destroy the misdn_cc_ds_info datastore payload
- *
- * \param[in] data the datastore payload, a reference to an misdn_cc_caller
- *
- * \details
- * Since the payload is a reference to an astobj2 object, we just decrement its
- * reference count.  Before doing so, we NULL out the channel pointer inside of
- * the misdn_cc_caller instance.  This function will be called in one of two
- * cases.  In both cases, we no longer need the channel pointer:
- *
- *  - The original channel that initialized call completion services, the same
- *    channel that is stored here, has been destroyed early.  This could happen
- *    if it transferred the mISDN channel, for example.
- *
- *  - The mISDN channel that had this datastore inherited on to it is now being
- *    destroyed.  If this is the case, then the call completion events have
- *    already occurred and the appropriate channel variables have already been
- *    set on the original channel that requested call completion services.
- */
-static void misdn_cc_ds_destroy(void *data)
-{
-	struct misdn_cc_caller *cc_caller = data;
-
-	ao2_lock(cc_caller);
-	cc_caller->chan = NULL;
-	ao2_unlock(cc_caller);
-
-	ao2_ref(cc_caller, -1);
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Duplicate the misdn_cc_ds_info datastore payload
- *
- * \param[in] data the datastore payload, a reference to an misdn_cc_caller
- *
- * \details
- * All we need to do is bump the reference count and return the same instance.
- *
- * \return A reference to an instance of a misdn_cc_caller
- */
-static void *misdn_cc_ds_duplicate(void *data)
-{
-	struct misdn_cc_caller *cc_caller = data;
-
-	ao2_ref(cc_caller, +1);
-
-	return cc_caller;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static const struct ast_datastore_info misdn_cc_ds_info = {
-	.type      = "misdn_cc",
-	.destroy   = misdn_cc_ds_destroy,
-	.duplicate = misdn_cc_ds_duplicate,
-};
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Set a channel var on the peer channel for call completion services
- *
- * \param[in] peer The peer that initialized call completion services
- * \param[in] var The variable name to set
- * \param[in] value The variable value to set
- *
- * This function may be called from outside of the channel thread.  It handles
- * the fact that the peer channel may be hung up and destroyed at any time.
- */
-static void misdn_cc_set_peer_var(struct misdn_cc_caller *peer, const char *var,
-	const char *value)
-{
-	ao2_lock(peer);
-
-	/*! \todo XXX This nastiness can go away once ast_channel is ref counted! */
-	while (peer->chan && ast_channel_trylock(peer->chan)) {
-		ao2_unlock(peer);
-		sched_yield();
-		ao2_lock(peer);
-	}
-
-	if (peer->chan) {
-		pbx_builtin_setvar_helper(peer->chan, var, value);
-		ast_channel_unlock(peer->chan);
-	}
-
-	ao2_unlock(peer);
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Get a reference to the CC caller if it exists
- */
-static struct misdn_cc_caller *misdn_cc_caller_get(struct ast_channel *chan)
-{
-	struct ast_datastore *datastore;
-	struct misdn_cc_caller *cc_caller;
-
-	ast_channel_lock(chan);
-
-	if (!(datastore = ast_channel_datastore_find(chan, &misdn_cc_ds_info, NULL))) {
-		ast_channel_unlock(chan);
-		return NULL;
-	}
-
-	ao2_ref(datastore->data, +1);
-	cc_caller = datastore->data;
-
-	ast_channel_unlock(chan);
-
-	return cc_caller;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Find the call completion record given the record id.
- *
- * \param record_id
- *
- * \retval pointer to found call completion record
- * \retval NULL if not found
- *
- * \note Assumes the misdn_cc_records_db lock is already obtained.
- */
-static struct misdn_cc_record *misdn_cc_find_by_id(long record_id)
-{
-	struct misdn_cc_record *current;
-
-	AST_LIST_TRAVERSE(&misdn_cc_records_db, current, list) {
-		if (current->record_id == record_id) {
-			/* Found the record */
-			break;
-		}
-	}
-
-	return current;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Find the call completion record given the port and call linkage id.
- *
- * \param port Logical port number
- * \param linkage_id Call linkage ID number from switch.
- *
- * \retval pointer to found call completion record
- * \retval NULL if not found
- *
- * \note Assumes the misdn_cc_records_db lock is already obtained.
- */
-static struct misdn_cc_record *misdn_cc_find_by_linkage(int port, int linkage_id)
-{
-	struct misdn_cc_record *current;
-
-	AST_LIST_TRAVERSE(&misdn_cc_records_db, current, list) {
-		if (current->port == port
-			&& !current->ptp
-			&& current->mode.ptmp.linkage_id == linkage_id) {
-			/* Found the record */
-			break;
-		}
-	}
-
-	return current;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Find the call completion record given the port and outstanding invocation id.
- *
- * \param port Logical port number
- * \param invoke_id Outstanding message invocation ID number.
- *
- * \retval pointer to found call completion record
- * \retval NULL if not found
- *
- * \note Assumes the misdn_cc_records_db lock is already obtained.
- */
-static struct misdn_cc_record *misdn_cc_find_by_invoke(int port, int invoke_id)
-{
-	struct misdn_cc_record *current;
-
-	AST_LIST_TRAVERSE(&misdn_cc_records_db, current, list) {
-		if (current->outstanding_message
-			&& current->invoke_id == invoke_id
-			&& current->port == port) {
-			/* Found the record */
-			break;
-		}
-	}
-
-	return current;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Find the call completion record given the port and CCBS reference id.
- *
- * \param port Logical port number
- * \param reference_id CCBS reference ID number from switch.
- *
- * \retval pointer to found call completion record
- * \retval NULL if not found
- *
- * \note Assumes the misdn_cc_records_db lock is already obtained.
- */
-static struct misdn_cc_record *misdn_cc_find_by_reference(int port, int reference_id)
-{
-	struct misdn_cc_record *current;
-
-	AST_LIST_TRAVERSE(&misdn_cc_records_db, current, list) {
-		if (current->activated
-			&& current->port == port
-			&& !current->ptp
-			&& current->mode.ptmp.reference_id == reference_id) {
-			/* Found the record */
-			break;
-		}
-	}
-
-	return current;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Find the call completion record given the B channel pointer
- *
- * \param bc B channel control structure pointer.
- *
- * \retval pointer to found call completion record
- * \retval NULL if not found
- *
- * \note Assumes the misdn_cc_records_db lock is already obtained.
- */
-static struct misdn_cc_record *misdn_cc_find_by_bc(const struct misdn_bchannel *bc)
-{
-	struct misdn_cc_record *current;
-
-	if (bc) {
-		AST_LIST_TRAVERSE(&misdn_cc_records_db, current, list) {
-			if (current->ptp
-				&& current->mode.ptp.bc == bc) {
-				/* Found the record */
-				break;
-			}
-		}
-	} else {
-		current = NULL;
-	}
-
-	return current;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Delete the given call completion record
- *
- * \param doomed Call completion record to destroy
- *
- * \note Assumes the misdn_cc_records_db lock is already obtained.
- */
-static void misdn_cc_delete(struct misdn_cc_record *doomed)
-{
-	struct misdn_cc_record *current;
-
-	AST_LIST_TRAVERSE_SAFE_BEGIN(&misdn_cc_records_db, current, list) {
-		if (current == doomed) {
-			AST_LIST_REMOVE_CURRENT(list);
-			ast_free(current);
-			return;
-		}
-	}
-	AST_LIST_TRAVERSE_SAFE_END;
-
-	/* The doomed node is not in the call completion database */
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Delete all old call completion records
- *
- * \note Assumes the misdn_cc_records_db lock is already obtained.
- */
-static void misdn_cc_remove_old(void)
-{
-	struct misdn_cc_record *current;
-	time_t now;
-
-	now = time(NULL);
-	AST_LIST_TRAVERSE_SAFE_BEGIN(&misdn_cc_records_db, current, list) {
-		if (MISDN_CC_RECORD_AGE_MAX < now - current->time_created) {
-			if (current->ptp && current->mode.ptp.bc) {
-				/* Close the old call-completion signaling link */
-				current->mode.ptp.bc->fac_out.Function = Fac_None;
-				current->mode.ptp.bc->out_cause = AST_CAUSE_NORMAL_CLEARING;
-				misdn_lib_send_event(current->mode.ptp.bc, EVENT_RELEASE_COMPLETE);
-			}
-
-			/* Remove the old call completion record */
-			AST_LIST_REMOVE_CURRENT(list);
-			ast_free(current);
-		}
-	}
-	AST_LIST_TRAVERSE_SAFE_END;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Allocate the next record id.
- *
- * \retval New record id on success.
- * \retval -1 on error.
- *
- * \note Assumes the misdn_cc_records_db lock is already obtained.
- */
-static long misdn_cc_record_id_new(void)
-{
-	long record_id;
-	long first_id;
-
-	record_id = ++misdn_cc_record_id;
-	first_id = record_id;
-	while (misdn_cc_find_by_id(record_id)) {
-		record_id = ++misdn_cc_record_id;
-		if (record_id == first_id) {
-			/*
-			 * We have a resource leak.
-			 * We should never need to allocate 64k records.
-			 */
-			chan_misdn_log(0, 0, " --> ERROR Too many call completion records!\n");
-			record_id = -1;
-			break;
-		}
-	}
-
-	return record_id;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Create a new call completion record
- *
- * \retval pointer to new call completion record
- * \retval NULL if failed
- *
- * \note Assumes the misdn_cc_records_db lock is already obtained.
- */
-static struct misdn_cc_record *misdn_cc_new(void)
-{
-	struct misdn_cc_record *cc_record;
-	long record_id;
-
-	misdn_cc_remove_old();
-
-	cc_record = ast_calloc(1, sizeof(*cc_record));
-	if (cc_record) {
-		record_id = misdn_cc_record_id_new();
-		if (record_id < 0) {
-			ast_free(cc_record);
-			return NULL;
-		}
-
-		/* Initialize the new record */
-		cc_record->record_id = record_id;
-		cc_record->port = -1;/* Invalid port so it will never be found this way */
-		cc_record->invoke_id = ++misdn_invoke_id;
-		cc_record->party_a_free = 1;/* Default User-A as free */
-		cc_record->error_code = FacError_None;
-		cc_record->reject_code = FacReject_None;
-		cc_record->time_created = time(NULL);
-
-		/* Insert the new record into the database */
-		AST_LIST_INSERT_HEAD(&misdn_cc_records_db, cc_record, list);
-	}
-	return cc_record;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Destroy the call completion record database
- */
-static void misdn_cc_destroy(void)
-{
-	struct misdn_cc_record *current;
-
-	while ((current = AST_LIST_REMOVE_HEAD(&misdn_cc_records_db, list))) {
-		/* Do a misdn_cc_delete(current) inline */
-		ast_free(current);
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Initialize the call completion record database
- */
-static void misdn_cc_init(void)
-{
-	misdn_cc_record_id = 0;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Check the status of an outstanding invocation request.
- *
- * \param data Points to an integer containing the call completion record id.
- *
- * \retval 0 if got a response.
- * \retval -1 if no response yet.
- */
-static int misdn_cc_response_check(void *data)
-{
-	int not_responded;
-	struct misdn_cc_record *cc_record;
-
-	AST_LIST_LOCK(&misdn_cc_records_db);
-	cc_record = misdn_cc_find_by_id(*(long *) data);
-	if (cc_record) {
-		if (cc_record->outstanding_message) {
-			not_responded = -1;
-		} else {
-			not_responded = 0;
-		}
-	} else {
-		/* No record so there is no response to check. */
-		not_responded = 0;
-	}
-	AST_LIST_UNLOCK(&misdn_cc_records_db);
-
-	return not_responded;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Wait for a response from the switch for an outstanding
- * invocation request.
- *
- * \param chan Asterisk channel to operate upon.
- * \param wait_seconds Number of seconds to wait
- * \param record_id Call completion record ID.
- */
-static void misdn_cc_response_wait(struct ast_channel *chan, int wait_seconds, long record_id)
-{
-	unsigned count;
-
-	for (count = 2 * MISDN_CC_REQUEST_WAIT_MAX; count--;) {
-		/* Sleep in 500 ms increments */
-		if (ast_safe_sleep_conditional(chan, 500, misdn_cc_response_check, &record_id) != 0) {
-			/* We got hung up or our response came in. */
-			break;
-		}
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Convert the mISDN reject code to a string
- *
- * \param code mISDN reject code.
- *
- * \return The mISDN reject code as a string
- */
-static const char *misdn_to_str_reject_code(enum FacRejectCode code)
-{
-	static const struct {
-		enum FacRejectCode code;
-		char *name;
-	} arr[] = {
-/* *INDENT-OFF* */
-		{ FacReject_None,                           "No reject occurred" },
-		{ FacReject_Unknown,                        "Unknown reject code" },
-
-		{ FacReject_Gen_UnrecognizedComponent,      "General: Unrecognized Component" },
-		{ FacReject_Gen_MistypedComponent,          "General: Mistyped Component" },
-		{ FacReject_Gen_BadlyStructuredComponent,   "General: Badly Structured Component" },
-
-		{ FacReject_Inv_DuplicateInvocation,        "Invoke: Duplicate Invocation" },
-		{ FacReject_Inv_UnrecognizedOperation,      "Invoke: Unrecognized Operation" },
-		{ FacReject_Inv_MistypedArgument,           "Invoke: Mistyped Argument" },
-		{ FacReject_Inv_ResourceLimitation,         "Invoke: Resource Limitation" },
-		{ FacReject_Inv_InitiatorReleasing,         "Invoke: Initiator Releasing" },
-		{ FacReject_Inv_UnrecognizedLinkedID,       "Invoke: Unrecognized Linked ID" },
-		{ FacReject_Inv_LinkedResponseUnexpected,   "Invoke: Linked Response Unexpected" },
-		{ FacReject_Inv_UnexpectedChildOperation,   "Invoke: Unexpected Child Operation" },
-
-		{ FacReject_Res_UnrecognizedInvocation,     "Result: Unrecognized Invocation" },
-		{ FacReject_Res_ResultResponseUnexpected,   "Result: Result Response Unexpected" },
-		{ FacReject_Res_MistypedResult,             "Result: Mistyped Result" },
-
-		{ FacReject_Err_UnrecognizedInvocation,     "Error: Unrecognized Invocation" },
-		{ FacReject_Err_ErrorResponseUnexpected,    "Error: Error Response Unexpected" },
-		{ FacReject_Err_UnrecognizedError,          "Error: Unrecognized Error" },
-		{ FacReject_Err_UnexpectedError,            "Error: Unexpected Error" },
-		{ FacReject_Err_MistypedParameter,          "Error: Mistyped Parameter" },
-/* *INDENT-ON* */
-	};
-
-	unsigned index;
-
-	for (index = 0; index < ARRAY_LEN(arr); ++index) {
-		if (arr[index].code == code) {
-			return arr[index].name;
-		}
-	}
-
-	return "unknown";
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Convert the mISDN error code to a string
- *
- * \param code mISDN error code.
- *
- * \return The mISDN error code as a string
- */
-static const char *misdn_to_str_error_code(enum FacErrorCode code)
-{
-	static const struct {
-		enum FacErrorCode code;
-		char *name;
-	} arr[] = {
-/* *INDENT-OFF* */
-		{ FacError_None,                            "No error occurred" },
-		{ FacError_Unknown,                         "Unknown OID error code" },
-
-		{ FacError_Gen_NotSubscribed,               "General: Not Subscribed" },
-		{ FacError_Gen_NotAvailable,                "General: Not Available" },
-		{ FacError_Gen_NotImplemented,              "General: Not Implemented" },
-		{ FacError_Gen_InvalidServedUserNr,         "General: Invalid Served User Number" },
-		{ FacError_Gen_InvalidCallState,            "General: Invalid Call State" },
-		{ FacError_Gen_BasicServiceNotProvided,     "General: Basic Service Not Provided" },
-		{ FacError_Gen_NotIncomingCall,             "General: Not Incoming Call" },
-		{ FacError_Gen_SupplementaryServiceInteractionNotAllowed,"General: Supplementary Service Interaction Not Allowed" },
-		{ FacError_Gen_ResourceUnavailable,         "General: Resource Unavailable" },
-
-		{ FacError_Div_InvalidDivertedToNr,         "Diversion: Invalid Diverted To Number" },
-		{ FacError_Div_SpecialServiceNr,            "Diversion: Special Service Number" },
-		{ FacError_Div_DiversionToServedUserNr,     "Diversion: Diversion To Served User Number" },
-		{ FacError_Div_IncomingCallAccepted,        "Diversion: Incoming Call Accepted" },
-		{ FacError_Div_NumberOfDiversionsExceeded,  "Diversion: Number Of Diversions Exceeded" },
-		{ FacError_Div_NotActivated,                "Diversion: Not Activated" },
-		{ FacError_Div_RequestAlreadyAccepted,      "Diversion: Request Already Accepted" },
-
-		{ FacError_AOC_NoChargingInfoAvailable,     "AOC: No Charging Info Available" },
-
-		{ FacError_CCBS_InvalidCallLinkageID,       "CCBS: Invalid Call Linkage ID" },
-		{ FacError_CCBS_InvalidCCBSReference,       "CCBS: Invalid CCBS Reference" },
-		{ FacError_CCBS_LongTermDenial,             "CCBS: Long Term Denial" },
-		{ FacError_CCBS_ShortTermDenial,            "CCBS: Short Term Denial" },
-		{ FacError_CCBS_IsAlreadyActivated,         "CCBS: Is Already Activated" },
-		{ FacError_CCBS_AlreadyAccepted,            "CCBS: Already Accepted" },
-		{ FacError_CCBS_OutgoingCCBSQueueFull,      "CCBS: Outgoing CCBS Queue Full" },
-		{ FacError_CCBS_CallFailureReasonNotBusy,   "CCBS: Call Failure Reason Not Busy" },
-		{ FacError_CCBS_NotReadyForCall,            "CCBS: Not Ready For Call" },
-
-		{ FacError_CCBS_T_LongTermDenial,           "CCBS-T: Long Term Denial" },
-		{ FacError_CCBS_T_ShortTermDenial,          "CCBS-T: Short Term Denial" },
-
-		{ FacError_ECT_LinkIdNotAssignedByNetwork,  "ECT: Link ID Not Assigned By Network" },
-/* *INDENT-ON* */
-	};
-
-	unsigned index;
-
-	for (index = 0; index < ARRAY_LEN(arr); ++index) {
-		if (arr[index].code == code) {
-			return arr[index].name;
-		}
-	}
-
-	return "unknown";
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Convert mISDN redirecting reason to diversion reason.
- *
- * \param reason mISDN redirecting reason code.
- *
- * \return Supported diversion reason code.
- */
-static unsigned misdn_to_diversion_reason(enum mISDN_REDIRECTING_REASON reason)
-{
-	unsigned diversion_reason;
-
-	switch (reason) {
-	case mISDN_REDIRECTING_REASON_CALL_FWD:
-		diversion_reason = 1;/* cfu */
-		break;
-	case mISDN_REDIRECTING_REASON_CALL_FWD_BUSY:
-		diversion_reason = 2;/* cfb */
-		break;
-	case mISDN_REDIRECTING_REASON_NO_REPLY:
-		diversion_reason = 3;/* cfnr */
-		break;
-	default:
-		diversion_reason = 0;/* unknown */
-		break;
-	}
-
-	return diversion_reason;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Convert diversion reason to mISDN redirecting reason
- *
- * \param diversion_reason Diversion reason to convert
- *
- * \return Supported redirecting reason code.
- */
-static enum mISDN_REDIRECTING_REASON diversion_reason_to_misdn(unsigned diversion_reason)
-{
-	enum mISDN_REDIRECTING_REASON reason;
-
-	switch (diversion_reason) {
-	case 1:/* cfu */
-		reason = mISDN_REDIRECTING_REASON_CALL_FWD;
-		break;
-	case 2:/* cfb */
-		reason = mISDN_REDIRECTING_REASON_CALL_FWD_BUSY;
-		break;
-	case 3:/* cfnr */
-		reason = mISDN_REDIRECTING_REASON_NO_REPLY;
-		break;
-	default:
-		reason = mISDN_REDIRECTING_REASON_UNKNOWN;
-		break;
-	}
-
-	return reason;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Convert the mISDN presentation to PresentedNumberUnscreened type
- *
- * \param presentation mISDN presentation to convert
- * \param number_present TRUE if the number is present
- *
- * \return PresentedNumberUnscreened type
- */
-static unsigned misdn_to_PresentedNumberUnscreened_type(int presentation, int number_present)
-{
-	unsigned type;
-
-	switch (presentation) {
-	case 0:/* allowed */
-		if (number_present) {
-			type = 0;/* presentationAllowedNumber */
-		} else {
-			type = 2;/* numberNotAvailableDueToInterworking */
-		}
-		break;
-	case 1:/* restricted */
-		if (number_present) {
-			type = 3;/* presentationRestrictedNumber */
-		} else {
-			type = 1;/* presentationRestricted */
-		}
-		break;
-	default:
-		type = 2;/* numberNotAvailableDueToInterworking */
-		break;
-	}
-
-	return type;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Convert the PresentedNumberUnscreened type to mISDN presentation
- *
- * \param type PresentedNumberUnscreened type
- *
- * \return mISDN presentation
- */
-static int PresentedNumberUnscreened_to_misdn_pres(unsigned type)
-{
-	int presentation;
-
-	switch (type) {
-	default:
-	case 0:/* presentationAllowedNumber */
-		presentation = 0;/* allowed */
-		break;
-
-	case 1:/* presentationRestricted */
-	case 3:/* presentationRestrictedNumber */
-		presentation = 1;/* restricted */
-		break;
-
-	case 2:/* numberNotAvailableDueToInterworking */
-		presentation = 2;/* unavailable */
-		break;
-	}
-
-	return presentation;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Convert the mISDN numbering plan to PartyNumber numbering plan
- *
- * \param number_plan mISDN numbering plan
- *
- * \return PartyNumber numbering plan
- */
-static unsigned misdn_to_PartyNumber_plan(enum mISDN_NUMBER_PLAN number_plan)
-{
-	unsigned party_plan;
-
-	switch (number_plan) {
-	default:
-	case NUMPLAN_UNKNOWN:
-		party_plan = 0;/* unknown */
-		break;
-
-	case NUMPLAN_ISDN:
-		party_plan = 1;/* public */
-		break;
-
-	case NUMPLAN_DATA:
-		party_plan = 3;/* data */
-		break;
-
-	case NUMPLAN_TELEX:
-		party_plan = 4;/* telex */
-		break;
-
-	case NUMPLAN_NATIONAL:
-		party_plan = 8;/* nationalStandard */
-		break;
-
-	case NUMPLAN_PRIVATE:
-		party_plan = 5;/* private */
-		break;
-	}
-
-	return party_plan;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Convert PartyNumber numbering plan to mISDN numbering plan
- *
- * \param party_plan PartyNumber numbering plan
- *
- * \return mISDN numbering plan
- */
-static enum mISDN_NUMBER_PLAN PartyNumber_to_misdn_plan(unsigned party_plan)
-{
-	enum mISDN_NUMBER_PLAN number_plan;
-
-	switch (party_plan) {
-	default:
-	case 0:/* unknown */
-		number_plan = NUMPLAN_UNKNOWN;
-		break;
-	case 1:/* public */
-		number_plan = NUMPLAN_ISDN;
-		break;
-	case 3:/* data */
-		number_plan = NUMPLAN_DATA;
-		break;
-	case 4:/* telex */
-		number_plan = NUMPLAN_TELEX;
-		break;
-	case 8:/* nationalStandard */
-		number_plan = NUMPLAN_NATIONAL;
-		break;
-	case 5:/* private */
-		number_plan = NUMPLAN_PRIVATE;
-		break;
-	}
-
-	return number_plan;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Convert mISDN type-of-number to PartyNumber public type-of-number
- *
- * \param ton mISDN type-of-number
- *
- * \return PartyNumber public type-of-number
- */
-static unsigned misdn_to_PartyNumber_ton_public(enum mISDN_NUMBER_TYPE ton)
-{
-	unsigned party_ton;
-
-	switch (ton) {
-	default:
-	case NUMTYPE_UNKNOWN:
-		party_ton = 0;/* unknown */
-		break;
-
-	case NUMTYPE_INTERNATIONAL:
-		party_ton = 1;/* internationalNumber */
-		break;
-
-	case NUMTYPE_NATIONAL:
-		party_ton = 2;/* nationalNumber */
-		break;
-
-	case NUMTYPE_NETWORK_SPECIFIC:
-		party_ton = 3;/* networkSpecificNumber */
-		break;
-
-	case NUMTYPE_SUBSCRIBER:
-		party_ton = 4;/* subscriberNumber */
-		break;
-
-	case NUMTYPE_ABBREVIATED:
-		party_ton = 6;/* abbreviatedNumber */
-		break;
-	}
-
-	return party_ton;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Convert the PartyNumber public type-of-number to mISDN type-of-number
- *
- * \param party_ton PartyNumber public type-of-number
- *
- * \return mISDN type-of-number
- */
-static enum mISDN_NUMBER_TYPE PartyNumber_to_misdn_ton_public(unsigned party_ton)
-{
-	enum mISDN_NUMBER_TYPE ton;
-
-	switch (party_ton) {
-	default:
-	case 0:/* unknown */
-		ton = NUMTYPE_UNKNOWN;
-		break;
-
-	case 1:/* internationalNumber */
-		ton = NUMTYPE_INTERNATIONAL;
-		break;
-
-	case 2:/* nationalNumber */
-		ton = NUMTYPE_NATIONAL;
-		break;
-
-	case 3:/* networkSpecificNumber */
-		ton = NUMTYPE_NETWORK_SPECIFIC;
-		break;
-
-	case 4:/* subscriberNumber */
-		ton = NUMTYPE_SUBSCRIBER;
-		break;
-
-	case 6:/* abbreviatedNumber */
-		ton = NUMTYPE_ABBREVIATED;
-		break;
-	}
-
-	return ton;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Convert mISDN type-of-number to PartyNumber private type-of-number
- *
- * \param ton mISDN type-of-number
- *
- * \return PartyNumber private type-of-number
- */
-static unsigned misdn_to_PartyNumber_ton_private(enum mISDN_NUMBER_TYPE ton)
-{
-	unsigned party_ton;
-
-	switch (ton) {
-	default:
-	case NUMTYPE_UNKNOWN:
-		party_ton = 0;/* unknown */
-		break;
-
-	case NUMTYPE_INTERNATIONAL:
-		party_ton = 1;/* level2RegionalNumber */
-		break;
-
-	case NUMTYPE_NATIONAL:
-		party_ton = 2;/* level1RegionalNumber */
-		break;
-
-	case NUMTYPE_NETWORK_SPECIFIC:
-		party_ton = 3;/* pTNSpecificNumber */
-		break;
-
-	case NUMTYPE_SUBSCRIBER:
-		party_ton = 4;/* localNumber */
-		break;
-
-	case NUMTYPE_ABBREVIATED:
-		party_ton = 6;/* abbreviatedNumber */
-		break;
-	}
-
-	return party_ton;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Convert the PartyNumber private type-of-number to mISDN type-of-number
- *
- * \param party_ton PartyNumber private type-of-number
- *
- * \return mISDN type-of-number
- */
-static enum mISDN_NUMBER_TYPE PartyNumber_to_misdn_ton_private(unsigned party_ton)
-{
-	enum mISDN_NUMBER_TYPE ton;
-
-	switch (party_ton) {
-	default:
-	case 0:/* unknown */
-		ton = NUMTYPE_UNKNOWN;
-		break;
-
-	case 1:/* level2RegionalNumber */
-		ton = NUMTYPE_INTERNATIONAL;
-		break;
-
-	case 2:/* level1RegionalNumber */
-		ton = NUMTYPE_NATIONAL;
-		break;
-
-	case 3:/* pTNSpecificNumber */
-		ton = NUMTYPE_NETWORK_SPECIFIC;
-		break;
-
-	case 4:/* localNumber */
-		ton = NUMTYPE_SUBSCRIBER;
-		break;
-
-	case 6:/* abbreviatedNumber */
-		ton = NUMTYPE_ABBREVIATED;
-		break;
-	}
-
-	return ton;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-/*!
- * \internal
- * \brief Convert the mISDN type of number code to a string
- *
- * \param number_type mISDN type of number code.
- *
- * \return The mISDN type of number code as a string
- */
-static const char *misdn_to_str_ton(enum mISDN_NUMBER_TYPE number_type)
-{
-	const char *str;
-
-	switch (number_type) {
-	default:
-	case NUMTYPE_UNKNOWN:
-		str = "Unknown";
-		break;
-
-	case NUMTYPE_INTERNATIONAL:
-		str = "International";
-		break;
-
-	case NUMTYPE_NATIONAL:
-		str = "National";
-		break;
-
-	case NUMTYPE_NETWORK_SPECIFIC:
-		str = "Network Specific";
-		break;
-
-	case NUMTYPE_SUBSCRIBER:
-		str = "Subscriber";
-		break;
-
-	case NUMTYPE_ABBREVIATED:
-		str = "Abbreviated";
-		break;
-	}
-
-	return str;
-}
-
-/*!
- * \internal
- * \brief Convert the mISDN type of number code to Asterisk type of number code
- *
- * \param number_type mISDN type of number code.
- *
- * \return Asterisk type of number code
- */
-static int misdn_to_ast_ton(enum mISDN_NUMBER_TYPE number_type)
-{
-	int ast_number_type;
-
-	switch (number_type) {
-	default:
-	case NUMTYPE_UNKNOWN:
-		ast_number_type = NUMTYPE_UNKNOWN << 4;
-		break;
-
-	case NUMTYPE_INTERNATIONAL:
-		ast_number_type = NUMTYPE_INTERNATIONAL << 4;
-		break;
-
-	case NUMTYPE_NATIONAL:
-		ast_number_type = NUMTYPE_NATIONAL << 4;
-		break;
-
-	case NUMTYPE_NETWORK_SPECIFIC:
-		ast_number_type = NUMTYPE_NETWORK_SPECIFIC << 4;
-		break;
-
-	case NUMTYPE_SUBSCRIBER:
-		ast_number_type = NUMTYPE_SUBSCRIBER << 4;
-		break;
-
-	case NUMTYPE_ABBREVIATED:
-		ast_number_type = NUMTYPE_ABBREVIATED << 4;
-		break;
-	}
-
-	return ast_number_type;
-}
-
-/*!
- * \internal
- * \brief Convert the Asterisk type of number code to mISDN type of number code
- *
- * \param ast_number_type Asterisk type of number code.
- *
- * \return mISDN type of number code
- */
-static enum mISDN_NUMBER_TYPE ast_to_misdn_ton(unsigned ast_number_type)
-{
-	enum mISDN_NUMBER_TYPE number_type;
-
-	switch ((ast_number_type >> 4) & 0x07) {
-	default:
-	case NUMTYPE_UNKNOWN:
-		number_type = NUMTYPE_UNKNOWN;
-		break;
-
-	case NUMTYPE_INTERNATIONAL:
-		number_type = NUMTYPE_INTERNATIONAL;
-		break;
-
-	case NUMTYPE_NATIONAL:
-		number_type = NUMTYPE_NATIONAL;
-		break;
-
-	case NUMTYPE_NETWORK_SPECIFIC:
-		number_type = NUMTYPE_NETWORK_SPECIFIC;
-		break;
-
-	case NUMTYPE_SUBSCRIBER:
-		number_type = NUMTYPE_SUBSCRIBER;
-		break;
-
-	case NUMTYPE_ABBREVIATED:
-		number_type = NUMTYPE_ABBREVIATED;
-		break;
-	}
-
-	return number_type;
-}
-
-/*!
- * \internal
- * \brief Convert the mISDN numbering plan code to a string
- *
- * \param number_plan mISDN numbering plan code.
- *
- * \return The mISDN numbering plan code as a string
- */
-static const char *misdn_to_str_plan(enum mISDN_NUMBER_PLAN number_plan)
-{
-	const char *str;
-
-	switch (number_plan) {
-	default:
-	case NUMPLAN_UNKNOWN:
-		str = "Unknown";
-		break;
-
-	case NUMPLAN_ISDN:
-		str = "ISDN";
-		break;
-
-	case NUMPLAN_DATA:
-		str = "Data";
-		break;
-
-	case NUMPLAN_TELEX:
-		str = "Telex";
-		break;
-
-	case NUMPLAN_NATIONAL:
-		str = "National";
-		break;
-
-	case NUMPLAN_PRIVATE:
-		str = "Private";
-		break;
-	}
-
-	return str;
-}
-
-/*!
- * \internal
- * \brief Convert the mISDN numbering plan code to Asterisk numbering plan code
- *
- * \param number_plan mISDN numbering plan code.
- *
- * \return Asterisk numbering plan code
- */
-static int misdn_to_ast_plan(enum mISDN_NUMBER_PLAN number_plan)
-{
-	int ast_number_plan;
-
-	switch (number_plan) {
-	default:
-	case NUMPLAN_UNKNOWN:
-		ast_number_plan = NUMPLAN_UNKNOWN;
-		break;
-
-	case NUMPLAN_ISDN:
-		ast_number_plan = NUMPLAN_ISDN;
-		break;
-
-	case NUMPLAN_DATA:
-		ast_number_plan = NUMPLAN_DATA;
-		break;
-
-	case NUMPLAN_TELEX:
-		ast_number_plan = NUMPLAN_TELEX;
-		break;
-
-	case NUMPLAN_NATIONAL:
-		ast_number_plan = NUMPLAN_NATIONAL;
-		break;
-
-	case NUMPLAN_PRIVATE:
-		ast_number_plan = NUMPLAN_PRIVATE;
-		break;
-	}
-
-	return ast_number_plan;
-}
-
-/*!
- * \internal
- * \brief Convert the Asterisk numbering plan code to mISDN numbering plan code
- *
- * \param ast_number_plan Asterisk numbering plan code.
- *
- * \return mISDN numbering plan code
- */
-static enum mISDN_NUMBER_PLAN ast_to_misdn_plan(unsigned ast_number_plan)
-{
-	enum mISDN_NUMBER_PLAN number_plan;
-
-	switch (ast_number_plan & 0x0F) {
-	default:
-	case NUMPLAN_UNKNOWN:
-		number_plan = NUMPLAN_UNKNOWN;
-		break;
-
-	case NUMPLAN_ISDN:
-		number_plan = NUMPLAN_ISDN;
-		break;
-
-	case NUMPLAN_DATA:
-		number_plan = NUMPLAN_DATA;
-		break;
-
-	case NUMPLAN_TELEX:
-		number_plan = NUMPLAN_TELEX;
-		break;
-
-	case NUMPLAN_NATIONAL:
-		number_plan = NUMPLAN_NATIONAL;
-		break;
-
-	case NUMPLAN_PRIVATE:
-		number_plan = NUMPLAN_PRIVATE;
-		break;
-	}
-
-	return number_plan;
-}
-
-/*!
- * \internal
- * \brief Convert the mISDN presentation code to a string
- *
- * \param presentation mISDN number presentation restriction code.
- *
- * \return The mISDN presentation code as a string
- */
-static const char *misdn_to_str_pres(int presentation)
-{
-	const char *str;
-
-	switch (presentation) {
-	case 0:
-		str = "Allowed";
-		break;
-
-	case 1:
-		str = "Restricted";
-		break;
-
-	case 2:
-		str = "Unavailable";
-		break;
-
-	default:
-		str = "Unknown";
-		break;
-	}
-
-	return str;
-}
-
-/*!
- * \internal
- * \brief Convert the mISDN presentation code to Asterisk presentation code
- *
- * \param presentation mISDN number presentation restriction code.
- *
- * \return Asterisk presentation code
- */
-static int misdn_to_ast_pres(int presentation)
-{
-	switch (presentation) {
-	default:
-	case 0:
-		presentation = AST_PRES_ALLOWED;
-		break;
-
-	case 1:
-		presentation = AST_PRES_RESTRICTED;
-		break;
-
-	case 2:
-		presentation = AST_PRES_UNAVAILABLE;
-		break;
-	}
-
-	return presentation;
-}
-
-/*!
- * \internal
- * \brief Convert the Asterisk presentation code to mISDN presentation code
- *
- * \param presentation Asterisk number presentation restriction code.
- *
- * \return mISDN presentation code
- */
-static int ast_to_misdn_pres(int presentation)
-{
-	switch (presentation & AST_PRES_RESTRICTION) {
-	default:
-	case AST_PRES_ALLOWED:
-		presentation = 0;
-		break;
-
-	case AST_PRES_RESTRICTED:
-		presentation = 1;
-		break;
-
-	case AST_PRES_UNAVAILABLE:
-		presentation = 2;
-		break;
-	}
-
-	return presentation;
-}
-
-/*!
- * \internal
- * \brief Convert the mISDN screening code to a string
- *
- * \param screening mISDN number screening code.
- *
- * \return The mISDN screening code as a string
- */
-static const char *misdn_to_str_screen(int screening)
-{
-	const char *str;
-
-	switch (screening) {
-	case 0:
-		str = "Unscreened";
-		break;
-
-	case 1:
-		str = "Passed Screen";
-		break;
-
-	case 2:
-		str = "Failed Screen";
-		break;
-
-	case 3:
-		str = "Network Number";
-		break;
-
-	default:
-		str = "Unknown";
-		break;
-	}
-
-	return str;
-}
-
-/*!
- * \internal
- * \brief Convert the mISDN screening code to Asterisk screening code
- *
- * \param screening mISDN number screening code.
- *
- * \return Asterisk screening code
- */
-static int misdn_to_ast_screen(int screening)
-{
-	switch (screening) {
-	default:
-	case 0:
-		screening = AST_PRES_USER_NUMBER_UNSCREENED;
-		break;
-
-	case 1:
-		screening = AST_PRES_USER_NUMBER_PASSED_SCREEN;
-		break;
-
-	case 2:
-		screening = AST_PRES_USER_NUMBER_FAILED_SCREEN;
-		break;
-
-	case 3:
-		screening = AST_PRES_NETWORK_NUMBER;
-		break;
-	}
-
-	return screening;
-}
-
-/*!
- * \internal
- * \brief Convert the Asterisk screening code to mISDN screening code
- *
- * \param screening Asterisk number screening code.
- *
- * \return mISDN screening code
- */
-static int ast_to_misdn_screen(int screening)
-{
-	switch (screening & AST_PRES_NUMBER_TYPE) {
-	default:
-	case AST_PRES_USER_NUMBER_UNSCREENED:
-		screening = 0;
-		break;
-
-	case AST_PRES_USER_NUMBER_PASSED_SCREEN:
-		screening = 1;
-		break;
-
-	case AST_PRES_USER_NUMBER_FAILED_SCREEN:
-		screening = 2;
-		break;
-
-	case AST_PRES_NETWORK_NUMBER:
-		screening = 3;
-		break;
-	}
-
-	return screening;
-}
-
-/*!
- * \internal
- * \brief Convert Asterisk redirecting reason to mISDN redirecting reason code.
- *
- * \param ast Asterisk redirecting reason code.
- *
- * \return mISDN reason code
- */
-static enum mISDN_REDIRECTING_REASON ast_to_misdn_reason(const enum AST_REDIRECTING_REASON ast)
-{
-	unsigned index;
-
-	static const struct misdn_reasons {
-		enum AST_REDIRECTING_REASON ast;
-		enum mISDN_REDIRECTING_REASON q931;
-	} misdn_reason_table[] = {
-	/* *INDENT-OFF* */
-		{ AST_REDIRECTING_REASON_UNKNOWN,        mISDN_REDIRECTING_REASON_UNKNOWN },
-		{ AST_REDIRECTING_REASON_USER_BUSY,      mISDN_REDIRECTING_REASON_CALL_FWD_BUSY },
-		{ AST_REDIRECTING_REASON_NO_ANSWER,      mISDN_REDIRECTING_REASON_NO_REPLY },
-		{ AST_REDIRECTING_REASON_UNAVAILABLE,    mISDN_REDIRECTING_REASON_NO_REPLY },
-		{ AST_REDIRECTING_REASON_UNCONDITIONAL,  mISDN_REDIRECTING_REASON_CALL_FWD },
-		{ AST_REDIRECTING_REASON_TIME_OF_DAY,    mISDN_REDIRECTING_REASON_UNKNOWN },
-		{ AST_REDIRECTING_REASON_DO_NOT_DISTURB, mISDN_REDIRECTING_REASON_UNKNOWN },
-		{ AST_REDIRECTING_REASON_DEFLECTION,     mISDN_REDIRECTING_REASON_DEFLECTION },
-		{ AST_REDIRECTING_REASON_FOLLOW_ME,      mISDN_REDIRECTING_REASON_UNKNOWN },
-		{ AST_REDIRECTING_REASON_OUT_OF_ORDER,   mISDN_REDIRECTING_REASON_OUT_OF_ORDER },
-		{ AST_REDIRECTING_REASON_AWAY,           mISDN_REDIRECTING_REASON_UNKNOWN },
-		{ AST_REDIRECTING_REASON_CALL_FWD_DTE,   mISDN_REDIRECTING_REASON_CALL_FWD_DTE }
-	/* *INDENT-ON* */
-	};
-
-	for (index = 0; index < ARRAY_LEN(misdn_reason_table); ++index) {
-		if (misdn_reason_table[index].ast == ast) {
-			return misdn_reason_table[index].q931;
-		}
-	}
-	return mISDN_REDIRECTING_REASON_UNKNOWN;
-}
-
-/*!
- * \internal
- * \brief Convert the mISDN redirecting reason to Asterisk redirecting reason code
- *
- * \param q931 mISDN redirecting reason code.
- *
- * \return Asterisk redirecting reason code
- */
-static enum AST_REDIRECTING_REASON misdn_to_ast_reason(const enum mISDN_REDIRECTING_REASON q931)
-{
-	enum AST_REDIRECTING_REASON ast;
-
-	switch (q931) {
-	default:
-	case mISDN_REDIRECTING_REASON_UNKNOWN:
-		ast = AST_REDIRECTING_REASON_UNKNOWN;
-		break;
-
-	case mISDN_REDIRECTING_REASON_CALL_FWD_BUSY:
-		ast = AST_REDIRECTING_REASON_USER_BUSY;
-		break;
-
-	case mISDN_REDIRECTING_REASON_NO_REPLY:
-		ast = AST_REDIRECTING_REASON_NO_ANSWER;
-		break;
-
-	case mISDN_REDIRECTING_REASON_DEFLECTION:
-		ast = AST_REDIRECTING_REASON_DEFLECTION;
-		break;
-
-	case mISDN_REDIRECTING_REASON_OUT_OF_ORDER:
-		ast = AST_REDIRECTING_REASON_OUT_OF_ORDER;
-		break;
-
-	case mISDN_REDIRECTING_REASON_CALL_FWD_DTE:
-		ast = AST_REDIRECTING_REASON_CALL_FWD_DTE;
-		break;
-
-	case mISDN_REDIRECTING_REASON_CALL_FWD:
-		ast = AST_REDIRECTING_REASON_UNCONDITIONAL;
-		break;
-	}
-
-	return ast;
-}
-
-
-
-struct allowed_bearers {
-	char *name;         /*!< Bearer capability name string used in /etc/misdn.conf allowed_bearers */
-	char *display;      /*!< Bearer capability displayable name */
-	int cap;            /*!< SETUP message bearer capability field code value */
-	int deprecated;     /*!< TRUE if this entry is deprecated. (Misspelled or bad name to use) */
-};
-
-/* *INDENT-OFF* */
-static const struct allowed_bearers allowed_bearers_array[] = {
-	/* Name,                      Displayable Name       Bearer Capability,                    Deprecated */
-	{ "speech",                  "Speech",               INFO_CAPABILITY_SPEECH,               0 },
-	{ "3_1khz",                  "3.1KHz Audio",         INFO_CAPABILITY_AUDIO_3_1K,           0 },
-	{ "digital_unrestricted",    "Unrestricted Digital", INFO_CAPABILITY_DIGITAL_UNRESTRICTED, 0 },
-	{ "digital_restricted",      "Restricted Digital",   INFO_CAPABILITY_DIGITAL_RESTRICTED,   0 },
-	{ "digital_restriced",       "Restricted Digital",   INFO_CAPABILITY_DIGITAL_RESTRICTED,   1 }, /* Allow misspelling for backwards compatibility */
-	{ "video",                   "Video",                INFO_CAPABILITY_VIDEO,                0 }
-};
-/* *INDENT-ON* */
-
-static const char *bearer2str(int cap)
-{
-	unsigned index;
-
-	for (index = 0; index < ARRAY_LEN(allowed_bearers_array); ++index) {
-		if (allowed_bearers_array[index].cap == cap) {
-			return allowed_bearers_array[index].display;
-		}
-	}
-
-	return "Unknown Bearer";
-}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Fill in facility PartyNumber information
- *
- * \param party PartyNumber structure to fill in.
- * \param id Information to put in PartyNumber structure.
- */
-static void misdn_PartyNumber_fill(struct FacPartyNumber *party, const struct misdn_party_id *id)
-{
-	ast_copy_string((char *) party->Number, id->number, sizeof(party->Number));
-	party->LengthOfNumber = strlen((char *) party->Number);
-	party->Type = misdn_to_PartyNumber_plan(id->number_plan);
-	switch (party->Type) {
-	case 1:/* public */
-		party->TypeOfNumber = misdn_to_PartyNumber_ton_public(id->number_type);
-		break;
-	case 5:/* private */
-		party->TypeOfNumber = misdn_to_PartyNumber_ton_private(id->number_type);
-		break;
-	default:
-		party->TypeOfNumber = 0;/* Don't care */
-		break;
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Extract the information from PartyNumber
- *
- * \param id Where to put extracted PartyNumber information
- * \param party PartyNumber information to extract
- */
-static void misdn_PartyNumber_extract(struct misdn_party_id *id, const struct FacPartyNumber *party)
-{
-	if (party->LengthOfNumber) {
-		ast_copy_string(id->number, (char *) party->Number, sizeof(id->number));
-		id->number_plan = PartyNumber_to_misdn_plan(party->Type);
-		switch (party->Type) {
-		case 1:/* public */
-			id->number_type = PartyNumber_to_misdn_ton_public(party->TypeOfNumber);
-			break;
-		case 5:/* private */
-			id->number_type = PartyNumber_to_misdn_ton_private(party->TypeOfNumber);
-			break;
-		default:
-			id->number_type = NUMTYPE_UNKNOWN;
-			break;
-		}
-	} else {
-		/* Number not present */
-		id->number_type = NUMTYPE_UNKNOWN;
-		id->number_plan = NUMPLAN_ISDN;
-		id->number[0] = 0;
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Fill in facility Address information
- *
- * \param Address Address structure to fill in.
- * \param id Information to put in Address structure.
- */
-static void misdn_Address_fill(struct FacAddress *Address, const struct misdn_party_id *id)
-{
-	misdn_PartyNumber_fill(&Address->Party, id);
-
-	/* Subaddresses are not supported yet */
-	Address->Subaddress.Length = 0;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Fill in facility PresentedNumberUnscreened information
- *
- * \param presented PresentedNumberUnscreened structure to fill in.
- * \param id Information to put in PresentedNumberUnscreened structure.
- */
-static void misdn_PresentedNumberUnscreened_fill(struct FacPresentedNumberUnscreened *presented, const struct misdn_party_id *id)
-{
-	presented->Type = misdn_to_PresentedNumberUnscreened_type(id->presentation, id->number[0] ? 1 : 0);
-	misdn_PartyNumber_fill(&presented->Unscreened, id);
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Extract the information from PartyNumber
- *
- * \param id Where to put extracted PresentedNumberUnscreened information
- * \param presented PresentedNumberUnscreened information to extract
- */
-static void misdn_PresentedNumberUnscreened_extract(struct misdn_party_id *id, const struct FacPresentedNumberUnscreened *presented)
-{
-	id->presentation = PresentedNumberUnscreened_to_misdn_pres(presented->Type);
-	id->screening = 0;/* unscreened */
-	switch (presented->Type) {
-	case 0:/* presentationAllowedNumber */
-	case 3:/* presentationRestrictedNumber */
-		misdn_PartyNumber_extract(id, &presented->Unscreened);
-		break;
-	case 1:/* presentationRestricted */
-	case 2:/* numberNotAvailableDueToInterworking */
-	default:
-		/* Number not present (And uninitialized so do not even look at it!) */
-		id->number_type = NUMTYPE_UNKNOWN;
-		id->number_plan = NUMPLAN_ISDN;
-		id->number[0] = 0;
-		break;
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static const char Level_Spacing[] = "          ";/* Work for up to 10 levels */
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static void print_facility_PartyNumber(unsigned Level, const struct FacPartyNumber *Party, const struct misdn_bchannel *bc)
-{
-	if (Party->LengthOfNumber) {
-		const char *Spacing;
-
-		Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level];
-		chan_misdn_log(1, bc->port, " -->%s PartyNumber: Type:%d\n",
-			Spacing, Party->Type);
-		switch (Party->Type) {
-		case 0: /* Unknown PartyNumber */
-			chan_misdn_log(1, bc->port, " -->%s  Unknown: %s\n",
-				Spacing, Party->Number);
-			break;
-		case 1: /* Public PartyNumber */
-			chan_misdn_log(1, bc->port, " -->%s  Public TON:%d %s\n",
-				Spacing, Party->TypeOfNumber, Party->Number);
-			break;
-		case 2: /* NSAP encoded PartyNumber */
-			chan_misdn_log(1, bc->port, " -->%s  NSAP: %s\n",
-				Spacing, Party->Number);
-			break;
-		case 3: /* Data PartyNumber (Not used) */
-			chan_misdn_log(1, bc->port, " -->%s  Data: %s\n",
-				Spacing, Party->Number);
-			break;
-		case 4: /* Telex PartyNumber (Not used) */
-			chan_misdn_log(1, bc->port, " -->%s  Telex: %s\n",
-				Spacing, Party->Number);
-			break;
-		case 5: /* Private PartyNumber */
-			chan_misdn_log(1, bc->port, " -->%s  Private TON:%d %s\n",
-				Spacing, Party->TypeOfNumber, Party->Number);
-			break;
-		case 8: /* National Standard PartyNumber (Not used) */
-			chan_misdn_log(1, bc->port, " -->%s  National: %s\n",
-				Spacing, Party->Number);
-			break;
-		default:
-			break;
-		}
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static void print_facility_Subaddress(unsigned Level, const struct FacPartySubaddress *Subaddress, const struct misdn_bchannel *bc)
-{
-	if (Subaddress->Length) {
-		const char *Spacing;
-
-		Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level];
-		chan_misdn_log(1, bc->port, " -->%s Subaddress: Type:%d\n",
-			Spacing, Subaddress->Type);
-		switch (Subaddress->Type) {
-		case 0: /* UserSpecified */
-			if (Subaddress->u.UserSpecified.OddCountPresent) {
-				chan_misdn_log(1, bc->port, " -->%s  User BCD OddCount:%d NumOctets:%d\n",
-					Spacing, Subaddress->u.UserSpecified.OddCount, Subaddress->Length);
-			} else {
-				chan_misdn_log(1, bc->port, " -->%s  User: %s\n",
-					Spacing, Subaddress->u.UserSpecified.Information);
-			}
-			break;
-		case 1: /* NSAP */
-			chan_misdn_log(1, bc->port, " -->%s  NSAP: %s\n",
-				Spacing, Subaddress->u.Nsap);
-			break;
-		default:
-			break;
-		}
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static void print_facility_Address(unsigned Level, const struct FacAddress *Address, const struct misdn_bchannel *bc)
-{
-	print_facility_PartyNumber(Level, &Address->Party, bc);
-	print_facility_Subaddress(Level, &Address->Subaddress, bc);
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static void print_facility_PresentedNumberUnscreened(unsigned Level, const struct FacPresentedNumberUnscreened *Presented, const struct misdn_bchannel *bc)
-{
-	const char *Spacing;
-
-	Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level];
-	chan_misdn_log(1, bc->port, " -->%s Unscreened Type:%d\n", Spacing, Presented->Type);
-	switch (Presented->Type) {
-	case 0: /* presentationAllowedNumber */
-		chan_misdn_log(1, bc->port, " -->%s  Allowed:\n", Spacing);
-		print_facility_PartyNumber(Level + 2, &Presented->Unscreened, bc);
-		break;
-	case 1: /* presentationRestricted */
-		chan_misdn_log(1, bc->port, " -->%s  Restricted\n", Spacing);
-		break;
-	case 2: /* numberNotAvailableDueToInterworking */
-		chan_misdn_log(1, bc->port, " -->%s  Not Available\n", Spacing);
-		break;
-	case 3: /* presentationRestrictedNumber */
-		chan_misdn_log(1, bc->port, " -->%s  Restricted:\n", Spacing);
-		print_facility_PartyNumber(Level + 2, &Presented->Unscreened, bc);
-		break;
-	default:
-		break;
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static void print_facility_AddressScreened(unsigned Level, const struct FacAddressScreened *Address, const struct misdn_bchannel *bc)
-{
-	const char *Spacing;
-
-	Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level];
-	chan_misdn_log(1, bc->port, " -->%s ScreeningIndicator:%d\n", Spacing, Address->ScreeningIndicator);
-	print_facility_PartyNumber(Level, &Address->Party, bc);
-	print_facility_Subaddress(Level, &Address->Subaddress, bc);
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static void print_facility_PresentedAddressScreened(unsigned Level, const struct FacPresentedAddressScreened *Presented, const struct misdn_bchannel *bc)
-{
-	const char *Spacing;
-
-	Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level];
-	chan_misdn_log(1, bc->port, " -->%s Screened Type:%d\n", Spacing, Presented->Type);
-	switch (Presented->Type) {
-	case 0: /* presentationAllowedAddress */
-		chan_misdn_log(1, bc->port, " -->%s  Allowed:\n", Spacing);
-		print_facility_AddressScreened(Level + 2, &Presented->Address, bc);
-		break;
-	case 1: /* presentationRestricted */
-		chan_misdn_log(1, bc->port, " -->%s  Restricted\n", Spacing);
-		break;
-	case 2: /* numberNotAvailableDueToInterworking */
-		chan_misdn_log(1, bc->port, " -->%s  Not Available\n", Spacing);
-		break;
-	case 3: /* presentationRestrictedAddress */
-		chan_misdn_log(1, bc->port, " -->%s  Restricted:\n", Spacing);
-		print_facility_AddressScreened(Level + 2, &Presented->Address, bc);
-		break;
-	default:
-		break;
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static void print_facility_Q931_Bc_Hlc_Llc(unsigned Level, const struct Q931_Bc_Hlc_Llc *Q931ie, const struct misdn_bchannel *bc)
-{
-	const char *Spacing;
-
-	Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level];
-	chan_misdn_log(1, bc->port, " -->%s Q931ie:\n", Spacing);
-	if (Q931ie->Bc.Length) {
-		chan_misdn_log(1, bc->port, " -->%s  Bc Len:%d\n", Spacing, Q931ie->Bc.Length);
-	}
-	if (Q931ie->Hlc.Length) {
-		chan_misdn_log(1, bc->port, " -->%s  Hlc Len:%d\n", Spacing, Q931ie->Hlc.Length);
-	}
-	if (Q931ie->Llc.Length) {
-		chan_misdn_log(1, bc->port, " -->%s  Llc Len:%d\n", Spacing, Q931ie->Llc.Length);
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static void print_facility_Q931_Bc_Hlc_Llc_Uu(unsigned Level, const struct Q931_Bc_Hlc_Llc_Uu *Q931ie, const struct misdn_bchannel *bc)
-{
-	const char *Spacing;
-
-	Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level];
-	chan_misdn_log(1, bc->port, " -->%s Q931ie:\n", Spacing);
-	if (Q931ie->Bc.Length) {
-		chan_misdn_log(1, bc->port, " -->%s  Bc Len:%d\n", Spacing, Q931ie->Bc.Length);
-	}
-	if (Q931ie->Hlc.Length) {
-		chan_misdn_log(1, bc->port, " -->%s  Hlc Len:%d\n", Spacing, Q931ie->Hlc.Length);
-	}
-	if (Q931ie->Llc.Length) {
-		chan_misdn_log(1, bc->port, " -->%s  Llc Len:%d\n", Spacing, Q931ie->Llc.Length);
-	}
-	if (Q931ie->UserInfo.Length) {
-		chan_misdn_log(1, bc->port, " -->%s  UserInfo Len:%d\n", Spacing, Q931ie->UserInfo.Length);
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static void print_facility_CallInformation(unsigned Level, const struct FacCallInformation *CallInfo, const struct misdn_bchannel *bc)
-{
-	const char *Spacing;
-
-	Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level];
-	chan_misdn_log(1, bc->port, " -->%s CCBSReference:%d\n",
-		Spacing, CallInfo->CCBSReference);
-	chan_misdn_log(1, bc->port, " -->%s AddressOfB:\n", Spacing);
-	print_facility_Address(Level + 1, &CallInfo->AddressOfB, bc);
-	print_facility_Q931_Bc_Hlc_Llc(Level, &CallInfo->Q931ie, bc);
-	if (CallInfo->SubaddressOfA.Length) {
-		chan_misdn_log(1, bc->port, " -->%s SubaddressOfA:\n", Spacing);
-		print_facility_Subaddress(Level + 1, &CallInfo->SubaddressOfA, bc);
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static void print_facility_ServedUserNr(unsigned Level, const struct FacPartyNumber *Party, const struct misdn_bchannel *bc)
-{
-	const char *Spacing;
-
-	Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level];
-	if (Party->LengthOfNumber) {
-		print_facility_PartyNumber(Level, Party, bc);
-	} else {
-		chan_misdn_log(1, bc->port, " -->%s All Numbers\n", Spacing);
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static void print_facility_IntResult(unsigned Level, const struct FacForwardingRecord *ForwardingRecord, const struct misdn_bchannel *bc)
-{
-	const char *Spacing;
-
-	Spacing = &Level_Spacing[sizeof(Level_Spacing) - 1 - Level];
-	chan_misdn_log(1, bc->port, " -->%s Procedure:%d BasicService:%d\n",
-		Spacing,
-		ForwardingRecord->Procedure,
-		ForwardingRecord->BasicService);
-	chan_misdn_log(1, bc->port, " -->%s ForwardedTo:\n", Spacing);
-	print_facility_Address(Level + 1, &ForwardingRecord->ForwardedTo, bc);
-	chan_misdn_log(1, bc->port, " -->%s ServedUserNr:\n", Spacing);
-	print_facility_ServedUserNr(Level + 1, &ForwardingRecord->ServedUser, bc);
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-static void print_facility(const struct FacParm *fac, const struct misdn_bchannel *bc)
-{
-#if defined(AST_MISDN_ENHANCEMENTS)
-	unsigned Index;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-	switch (fac->Function) {
-#if defined(AST_MISDN_ENHANCEMENTS)
-	case Fac_ActivationDiversion:
-		chan_misdn_log(1, bc->port, " --> ActivationDiversion: InvokeID:%d\n",
-			fac->u.ActivationDiversion.InvokeID);
-		switch (fac->u.ActivationDiversion.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke: Procedure:%d BasicService:%d\n",
-				fac->u.ActivationDiversion.Component.Invoke.Procedure,
-				fac->u.ActivationDiversion.Component.Invoke.BasicService);
-			chan_misdn_log(1, bc->port, " -->   ForwardedTo:\n");
-			print_facility_Address(3, &fac->u.ActivationDiversion.Component.Invoke.ForwardedTo, bc);
-			chan_misdn_log(1, bc->port, " -->   ServedUserNr:\n");
-			print_facility_ServedUserNr(3, &fac->u.ActivationDiversion.Component.Invoke.ServedUser, bc);
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result\n");
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_DeactivationDiversion:
-		chan_misdn_log(1, bc->port, " --> DeactivationDiversion: InvokeID:%d\n",
-			fac->u.DeactivationDiversion.InvokeID);
-		switch (fac->u.DeactivationDiversion.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke: Procedure:%d BasicService:%d\n",
-				fac->u.DeactivationDiversion.Component.Invoke.Procedure,
-				fac->u.DeactivationDiversion.Component.Invoke.BasicService);
-			chan_misdn_log(1, bc->port, " -->   ServedUserNr:\n");
-			print_facility_ServedUserNr(3, &fac->u.DeactivationDiversion.Component.Invoke.ServedUser, bc);
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result\n");
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_ActivationStatusNotificationDiv:
-		chan_misdn_log(1, bc->port, " --> ActivationStatusNotificationDiv: InvokeID:%d Procedure:%d BasicService:%d\n",
-			fac->u.ActivationStatusNotificationDiv.InvokeID,
-			fac->u.ActivationStatusNotificationDiv.Procedure,
-			fac->u.ActivationStatusNotificationDiv.BasicService);
-		chan_misdn_log(1, bc->port, " -->  ForwardedTo:\n");
-		print_facility_Address(2, &fac->u.ActivationStatusNotificationDiv.ForwardedTo, bc);
-		chan_misdn_log(1, bc->port, " -->  ServedUserNr:\n");
-		print_facility_ServedUserNr(2, &fac->u.ActivationStatusNotificationDiv.ServedUser, bc);
-		break;
-	case Fac_DeactivationStatusNotificationDiv:
-		chan_misdn_log(1, bc->port, " --> DeactivationStatusNotificationDiv: InvokeID:%d Procedure:%d BasicService:%d\n",
-			fac->u.DeactivationStatusNotificationDiv.InvokeID,
-			fac->u.DeactivationStatusNotificationDiv.Procedure,
-			fac->u.DeactivationStatusNotificationDiv.BasicService);
-		chan_misdn_log(1, bc->port, " -->  ServedUserNr:\n");
-		print_facility_ServedUserNr(2, &fac->u.DeactivationStatusNotificationDiv.ServedUser, bc);
-		break;
-	case Fac_InterrogationDiversion:
-		chan_misdn_log(1, bc->port, " --> InterrogationDiversion: InvokeID:%d\n",
-			fac->u.InterrogationDiversion.InvokeID);
-		switch (fac->u.InterrogationDiversion.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke: Procedure:%d BasicService:%d\n",
-				fac->u.InterrogationDiversion.Component.Invoke.Procedure,
-				fac->u.InterrogationDiversion.Component.Invoke.BasicService);
-			chan_misdn_log(1, bc->port, " -->   ServedUserNr:\n");
-			print_facility_ServedUserNr(3, &fac->u.InterrogationDiversion.Component.Invoke.ServedUser, bc);
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result:\n");
-			if (fac->u.InterrogationDiversion.Component.Result.NumRecords) {
-				for (Index = 0; Index < fac->u.InterrogationDiversion.Component.Result.NumRecords; ++Index) {
-					chan_misdn_log(1, bc->port, " -->   IntResult[%d]:\n", Index);
-					print_facility_IntResult(3, &fac->u.InterrogationDiversion.Component.Result.List[Index], bc);
-				}
-			}
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_DiversionInformation:
-		chan_misdn_log(1, bc->port, " --> DiversionInformation: InvokeID:%d Reason:%d BasicService:%d\n",
-			fac->u.DiversionInformation.InvokeID,
-			fac->u.DiversionInformation.DiversionReason,
-			fac->u.DiversionInformation.BasicService);
-		if (fac->u.DiversionInformation.ServedUserSubaddress.Length) {
-			chan_misdn_log(1, bc->port, " -->  ServedUserSubaddress:\n");
-			print_facility_Subaddress(2, &fac->u.DiversionInformation.ServedUserSubaddress, bc);
-		}
-		if (fac->u.DiversionInformation.CallingAddressPresent) {
-			chan_misdn_log(1, bc->port, " -->  CallingAddress:\n");
-			print_facility_PresentedAddressScreened(2, &fac->u.DiversionInformation.CallingAddress, bc);
-		}
-		if (fac->u.DiversionInformation.OriginalCalledPresent) {
-			chan_misdn_log(1, bc->port, " -->  OriginalCalledNr:\n");
-			print_facility_PresentedNumberUnscreened(2, &fac->u.DiversionInformation.OriginalCalled, bc);
-		}
-		if (fac->u.DiversionInformation.LastDivertingPresent) {
-			chan_misdn_log(1, bc->port, " -->  LastDivertingNr:\n");
-			print_facility_PresentedNumberUnscreened(2, &fac->u.DiversionInformation.LastDiverting, bc);
-		}
-		if (fac->u.DiversionInformation.LastDivertingReasonPresent) {
-			chan_misdn_log(1, bc->port, " -->  LastDivertingReason:%d\n", fac->u.DiversionInformation.LastDivertingReason);
-		}
-		if (fac->u.DiversionInformation.UserInfo.Length) {
-			chan_misdn_log(1, bc->port, " -->  UserInfo Length:%d\n", fac->u.DiversionInformation.UserInfo.Length);
-		}
-		break;
-	case Fac_CallDeflection:
-		chan_misdn_log(1, bc->port, " --> CallDeflection: InvokeID:%d\n",
-			fac->u.CallDeflection.InvokeID);
-		switch (fac->u.CallDeflection.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke:\n");
-			if (fac->u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUserPresent) {
-				chan_misdn_log(1, bc->port, " -->   PresentationAllowed:%d\n",
-					fac->u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUser);
-			}
-			chan_misdn_log(1, bc->port, " -->   DeflectionAddress:\n");
-			print_facility_Address(3, &fac->u.CallDeflection.Component.Invoke.Deflection, bc);
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result\n");
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_CallRerouteing:
-		chan_misdn_log(1, bc->port, " --> CallRerouteing: InvokeID:%d\n",
-			fac->u.CallRerouteing.InvokeID);
-		switch (fac->u.CallRerouteing.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke: Reason:%d Counter:%d\n",
-				fac->u.CallRerouteing.Component.Invoke.ReroutingReason,
-				fac->u.CallRerouteing.Component.Invoke.ReroutingCounter);
-			chan_misdn_log(1, bc->port, " -->   CalledAddress:\n");
-			print_facility_Address(3, &fac->u.CallRerouteing.Component.Invoke.CalledAddress, bc);
-			print_facility_Q931_Bc_Hlc_Llc_Uu(2, &fac->u.CallRerouteing.Component.Invoke.Q931ie, bc);
-			chan_misdn_log(1, bc->port, " -->   LastReroutingNr:\n");
-			print_facility_PresentedNumberUnscreened(3, &fac->u.CallRerouteing.Component.Invoke.LastRerouting, bc);
-			chan_misdn_log(1, bc->port, " -->   SubscriptionOption:%d\n",
-				fac->u.CallRerouteing.Component.Invoke.SubscriptionOption);
-			if (fac->u.CallRerouteing.Component.Invoke.CallingPartySubaddress.Length) {
-				chan_misdn_log(1, bc->port, " -->   CallingParty:\n");
-				print_facility_Subaddress(3, &fac->u.CallRerouteing.Component.Invoke.CallingPartySubaddress, bc);
-			}
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result\n");
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_InterrogateServedUserNumbers:
-		chan_misdn_log(1, bc->port, " --> InterrogateServedUserNumbers: InvokeID:%d\n",
-			fac->u.InterrogateServedUserNumbers.InvokeID);
-		switch (fac->u.InterrogateServedUserNumbers.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke\n");
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result:\n");
-			if (fac->u.InterrogateServedUserNumbers.Component.Result.NumRecords) {
-				for (Index = 0; Index < fac->u.InterrogateServedUserNumbers.Component.Result.NumRecords; ++Index) {
-					chan_misdn_log(1, bc->port, " -->   ServedUserNr[%d]:\n", Index);
-					print_facility_PartyNumber(3, &fac->u.InterrogateServedUserNumbers.Component.Result.List[Index], bc);
-				}
-			}
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_DivertingLegInformation1:
-		chan_misdn_log(1, bc->port, " --> DivertingLegInformation1: InvokeID:%d Reason:%d SubscriptionOption:%d\n",
-			fac->u.DivertingLegInformation1.InvokeID,
-			fac->u.DivertingLegInformation1.DiversionReason,
-			fac->u.DivertingLegInformation1.SubscriptionOption);
-		if (fac->u.DivertingLegInformation1.DivertedToPresent) {
-			chan_misdn_log(1, bc->port, " -->  DivertedToNr:\n");
-			print_facility_PresentedNumberUnscreened(2, &fac->u.DivertingLegInformation1.DivertedTo, bc);
-		}
-		break;
-	case Fac_DivertingLegInformation2:
-		chan_misdn_log(1, bc->port, " --> DivertingLegInformation2: InvokeID:%d Reason:%d Count:%d\n",
-			fac->u.DivertingLegInformation2.InvokeID,
-			fac->u.DivertingLegInformation2.DiversionReason,
-			fac->u.DivertingLegInformation2.DiversionCounter);
-		if (fac->u.DivertingLegInformation2.DivertingPresent) {
-			chan_misdn_log(1, bc->port, " -->  DivertingNr:\n");
-			print_facility_PresentedNumberUnscreened(2, &fac->u.DivertingLegInformation2.Diverting, bc);
-		}
-		if (fac->u.DivertingLegInformation2.OriginalCalledPresent) {
-			chan_misdn_log(1, bc->port, " -->  OriginalCalledNr:\n");
-			print_facility_PresentedNumberUnscreened(2, &fac->u.DivertingLegInformation2.OriginalCalled, bc);
-		}
-		break;
-	case Fac_DivertingLegInformation3:
-		chan_misdn_log(1, bc->port, " --> DivertingLegInformation3: InvokeID:%d PresentationAllowed:%d\n",
-			fac->u.DivertingLegInformation3.InvokeID,
-			fac->u.DivertingLegInformation3.PresentationAllowedIndicator);
-		break;
-
-#else	/* !defined(AST_MISDN_ENHANCEMENTS) */
-
-	case Fac_CD:
-		chan_misdn_log(1, bc->port, " --> calldeflect to: %s, presentable: %s\n", fac->u.CDeflection.DeflectedToNumber,
-			fac->u.CDeflection.PresentationAllowed ? "yes" : "no");
-		break;
-#endif	/* !defined(AST_MISDN_ENHANCEMENTS) */
-	case Fac_AOCDCurrency:
-		if (fac->u.AOCDcur.chargeNotAvailable) {
-			chan_misdn_log(1, bc->port, " --> AOCD currency: charge not available\n");
-		} else if (fac->u.AOCDcur.freeOfCharge) {
-			chan_misdn_log(1, bc->port, " --> AOCD currency: free of charge\n");
-		} else if (fac->u.AOCDchu.billingId >= 0) {
-			chan_misdn_log(1, bc->port, " --> AOCD currency: currency:%s amount:%d multiplier:%d typeOfChargingInfo:%s billingId:%d\n",
-				fac->u.AOCDcur.currency, fac->u.AOCDcur.currencyAmount, fac->u.AOCDcur.multiplier,
-				(fac->u.AOCDcur.typeOfChargingInfo == 0) ? "subTotal" : "total", fac->u.AOCDcur.billingId);
-		} else {
-			chan_misdn_log(1, bc->port, " --> AOCD currency: currency:%s amount:%d multiplier:%d typeOfChargingInfo:%s\n",
-				fac->u.AOCDcur.currency, fac->u.AOCDcur.currencyAmount, fac->u.AOCDcur.multiplier,
-				(fac->u.AOCDcur.typeOfChargingInfo == 0) ? "subTotal" : "total");
-		}
-		break;
-	case Fac_AOCDChargingUnit:
-		if (fac->u.AOCDchu.chargeNotAvailable) {
-			chan_misdn_log(1, bc->port, " --> AOCD charging unit: charge not available\n");
-		} else if (fac->u.AOCDchu.freeOfCharge) {
-			chan_misdn_log(1, bc->port, " --> AOCD charging unit: free of charge\n");
-		} else if (fac->u.AOCDchu.billingId >= 0) {
-			chan_misdn_log(1, bc->port, " --> AOCD charging unit: recordedUnits:%d typeOfChargingInfo:%s billingId:%d\n",
-				fac->u.AOCDchu.recordedUnits, (fac->u.AOCDchu.typeOfChargingInfo == 0) ? "subTotal" : "total", fac->u.AOCDchu.billingId);
-		} else {
-			chan_misdn_log(1, bc->port, " --> AOCD charging unit: recordedUnits:%d typeOfChargingInfo:%s\n",
-				fac->u.AOCDchu.recordedUnits, (fac->u.AOCDchu.typeOfChargingInfo == 0) ? "subTotal" : "total");
-		}
-		break;
-#if defined(AST_MISDN_ENHANCEMENTS)
-	case Fac_ERROR:
-		chan_misdn_log(1, bc->port, " --> ERROR: InvokeID:%d, Code:0x%02x\n",
-			fac->u.ERROR.invokeId, fac->u.ERROR.errorValue);
-		break;
-	case Fac_RESULT:
-		chan_misdn_log(1, bc->port, " --> RESULT: InvokeID:%d\n",
-			fac->u.RESULT.InvokeID);
-		break;
-	case Fac_REJECT:
-		if (fac->u.REJECT.InvokeIDPresent) {
-			chan_misdn_log(1, bc->port, " --> REJECT: InvokeID:%d, Code:0x%02x\n",
-				fac->u.REJECT.InvokeID, fac->u.REJECT.Code);
-		} else {
-			chan_misdn_log(1, bc->port, " --> REJECT: Code:0x%02x\n",
-				fac->u.REJECT.Code);
-		}
-		break;
-	case Fac_EctExecute:
-		chan_misdn_log(1, bc->port, " --> EctExecute: InvokeID:%d\n",
-			fac->u.EctExecute.InvokeID);
-		break;
-	case Fac_ExplicitEctExecute:
-		chan_misdn_log(1, bc->port, " --> ExplicitEctExecute: InvokeID:%d LinkID:%d\n",
-			fac->u.ExplicitEctExecute.InvokeID,
-			fac->u.ExplicitEctExecute.LinkID);
-		break;
-	case Fac_RequestSubaddress:
-		chan_misdn_log(1, bc->port, " --> RequestSubaddress: InvokeID:%d\n",
-			fac->u.RequestSubaddress.InvokeID);
-		break;
-	case Fac_SubaddressTransfer:
-		chan_misdn_log(1, bc->port, " --> SubaddressTransfer: InvokeID:%d\n",
-			fac->u.SubaddressTransfer.InvokeID);
-		print_facility_Subaddress(1, &fac->u.SubaddressTransfer.Subaddress, bc);
-		break;
-	case Fac_EctLinkIdRequest:
-		chan_misdn_log(1, bc->port, " --> EctLinkIdRequest: InvokeID:%d\n",
-			fac->u.EctLinkIdRequest.InvokeID);
-		switch (fac->u.EctLinkIdRequest.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke\n");
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result: LinkID:%d\n",
-				fac->u.EctLinkIdRequest.Component.Result.LinkID);
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_EctInform:
-		chan_misdn_log(1, bc->port, " --> EctInform: InvokeID:%d Status:%d\n",
-			fac->u.EctInform.InvokeID,
-			fac->u.EctInform.Status);
-		if (fac->u.EctInform.RedirectionPresent) {
-			chan_misdn_log(1, bc->port, " -->  Redirection Number\n");
-			print_facility_PresentedNumberUnscreened(2, &fac->u.EctInform.Redirection, bc);
-		}
-		break;
-	case Fac_EctLoopTest:
-		chan_misdn_log(1, bc->port, " --> EctLoopTest: InvokeID:%d\n",
-			fac->u.EctLoopTest.InvokeID);
-		switch (fac->u.EctLoopTest.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke: CallTransferID:%d\n",
-				fac->u.EctLoopTest.Component.Invoke.CallTransferID);
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result: LoopResult:%d\n",
-				fac->u.EctLoopTest.Component.Result.LoopResult);
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_StatusRequest:
-		chan_misdn_log(1, bc->port, " --> StatusRequest: InvokeID:%d\n",
-			fac->u.StatusRequest.InvokeID);
-		switch (fac->u.StatusRequest.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke: Compatibility:%d\n",
-				fac->u.StatusRequest.Component.Invoke.CompatibilityMode);
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result: Status:%d\n",
-				fac->u.StatusRequest.Component.Result.Status);
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_CallInfoRetain:
-		chan_misdn_log(1, bc->port, " --> CallInfoRetain: InvokeID:%d, LinkageID:%d\n",
-			fac->u.CallInfoRetain.InvokeID, fac->u.CallInfoRetain.CallLinkageID);
-		break;
-	case Fac_CCBSDeactivate:
-		chan_misdn_log(1, bc->port, " --> CCBSDeactivate: InvokeID:%d\n",
-			fac->u.CCBSDeactivate.InvokeID);
-		switch (fac->u.CCBSDeactivate.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke: CCBSReference:%d\n",
-				fac->u.CCBSDeactivate.Component.Invoke.CCBSReference);
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result\n");
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_CCBSErase:
-		chan_misdn_log(1, bc->port, " --> CCBSErase: InvokeID:%d, CCBSReference:%d RecallMode:%d, Reason:%d\n",
-			fac->u.CCBSErase.InvokeID, fac->u.CCBSErase.CCBSReference,
-			fac->u.CCBSErase.RecallMode, fac->u.CCBSErase.Reason);
-		chan_misdn_log(1, bc->port, " -->  AddressOfB\n");
-		print_facility_Address(2, &fac->u.CCBSErase.AddressOfB, bc);
-		print_facility_Q931_Bc_Hlc_Llc(1, &fac->u.CCBSErase.Q931ie, bc);
-		break;
-	case Fac_CCBSRemoteUserFree:
-		chan_misdn_log(1, bc->port, " --> CCBSRemoteUserFree: InvokeID:%d, CCBSReference:%d RecallMode:%d\n",
-			fac->u.CCBSRemoteUserFree.InvokeID, fac->u.CCBSRemoteUserFree.CCBSReference,
-			fac->u.CCBSRemoteUserFree.RecallMode);
-		chan_misdn_log(1, bc->port, " -->  AddressOfB\n");
-		print_facility_Address(2, &fac->u.CCBSRemoteUserFree.AddressOfB, bc);
-		print_facility_Q931_Bc_Hlc_Llc(1, &fac->u.CCBSRemoteUserFree.Q931ie, bc);
-		break;
-	case Fac_CCBSCall:
-		chan_misdn_log(1, bc->port, " --> CCBSCall: InvokeID:%d, CCBSReference:%d\n",
-			fac->u.CCBSCall.InvokeID, fac->u.CCBSCall.CCBSReference);
-		break;
-	case Fac_CCBSStatusRequest:
-		chan_misdn_log(1, bc->port, " --> CCBSStatusRequest: InvokeID:%d\n",
-			fac->u.CCBSStatusRequest.InvokeID);
-		switch (fac->u.CCBSStatusRequest.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke: CCBSReference:%d RecallMode:%d\n",
-				fac->u.CCBSStatusRequest.Component.Invoke.CCBSReference,
-				fac->u.CCBSStatusRequest.Component.Invoke.RecallMode);
-			print_facility_Q931_Bc_Hlc_Llc(2, &fac->u.CCBSStatusRequest.Component.Invoke.Q931ie, bc);
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result: Free:%d\n",
-				fac->u.CCBSStatusRequest.Component.Result.Free);
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_CCBSBFree:
-		chan_misdn_log(1, bc->port, " --> CCBSBFree: InvokeID:%d, CCBSReference:%d RecallMode:%d\n",
-			fac->u.CCBSBFree.InvokeID, fac->u.CCBSBFree.CCBSReference,
-			fac->u.CCBSBFree.RecallMode);
-		chan_misdn_log(1, bc->port, " -->  AddressOfB\n");
-		print_facility_Address(2, &fac->u.CCBSBFree.AddressOfB, bc);
-		print_facility_Q931_Bc_Hlc_Llc(1, &fac->u.CCBSBFree.Q931ie, bc);
-		break;
-	case Fac_EraseCallLinkageID:
-		chan_misdn_log(1, bc->port, " --> EraseCallLinkageID: InvokeID:%d, LinkageID:%d\n",
-			fac->u.EraseCallLinkageID.InvokeID, fac->u.EraseCallLinkageID.CallLinkageID);
-		break;
-	case Fac_CCBSStopAlerting:
-		chan_misdn_log(1, bc->port, " --> CCBSStopAlerting: InvokeID:%d, CCBSReference:%d\n",
-			fac->u.CCBSStopAlerting.InvokeID, fac->u.CCBSStopAlerting.CCBSReference);
-		break;
-	case Fac_CCBSRequest:
-		chan_misdn_log(1, bc->port, " --> CCBSRequest: InvokeID:%d\n",
-			fac->u.CCBSRequest.InvokeID);
-		switch (fac->u.CCBSRequest.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke: LinkageID:%d\n",
-				fac->u.CCBSRequest.Component.Invoke.CallLinkageID);
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result: CCBSReference:%d RecallMode:%d\n",
-				fac->u.CCBSRequest.Component.Result.CCBSReference,
-				fac->u.CCBSRequest.Component.Result.RecallMode);
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_CCBSInterrogate:
-		chan_misdn_log(1, bc->port, " --> CCBSInterrogate: InvokeID:%d\n",
-			fac->u.CCBSInterrogate.InvokeID);
-		switch (fac->u.CCBSInterrogate.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke\n");
-			if (fac->u.CCBSInterrogate.Component.Invoke.CCBSReferencePresent) {
-				chan_misdn_log(1, bc->port, " -->   CCBSReference:%d\n",
-					fac->u.CCBSInterrogate.Component.Invoke.CCBSReference);
-			}
-			if (fac->u.CCBSInterrogate.Component.Invoke.AParty.LengthOfNumber) {
-				chan_misdn_log(1, bc->port, " -->   AParty\n");
-				print_facility_PartyNumber(3, &fac->u.CCBSInterrogate.Component.Invoke.AParty, bc);
-			}
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result: RecallMode:%d\n",
-				fac->u.CCBSInterrogate.Component.Result.RecallMode);
-			if (fac->u.CCBSInterrogate.Component.Result.NumRecords) {
-				for (Index = 0; Index < fac->u.CCBSInterrogate.Component.Result.NumRecords; ++Index) {
-					chan_misdn_log(1, bc->port, " -->   CallDetails[%d]:\n", Index);
-					print_facility_CallInformation(3, &fac->u.CCBSInterrogate.Component.Result.CallDetails[Index], bc);
-				}
-			}
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_CCNRRequest:
-		chan_misdn_log(1, bc->port, " --> CCNRRequest: InvokeID:%d\n",
-			fac->u.CCNRRequest.InvokeID);
-		switch (fac->u.CCNRRequest.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke: LinkageID:%d\n",
-				fac->u.CCNRRequest.Component.Invoke.CallLinkageID);
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result: CCBSReference:%d RecallMode:%d\n",
-				fac->u.CCNRRequest.Component.Result.CCBSReference,
-				fac->u.CCNRRequest.Component.Result.RecallMode);
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_CCNRInterrogate:
-		chan_misdn_log(1, bc->port, " --> CCNRInterrogate: InvokeID:%d\n",
-			fac->u.CCNRInterrogate.InvokeID);
-		switch (fac->u.CCNRInterrogate.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke\n");
-			if (fac->u.CCNRInterrogate.Component.Invoke.CCBSReferencePresent) {
-				chan_misdn_log(1, bc->port, " -->   CCBSReference:%d\n",
-					fac->u.CCNRInterrogate.Component.Invoke.CCBSReference);
-			}
-			if (fac->u.CCNRInterrogate.Component.Invoke.AParty.LengthOfNumber) {
-				chan_misdn_log(1, bc->port, " -->   AParty\n");
-				print_facility_PartyNumber(3, &fac->u.CCNRInterrogate.Component.Invoke.AParty, bc);
-			}
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result: RecallMode:%d\n",
-				fac->u.CCNRInterrogate.Component.Result.RecallMode);
-			if (fac->u.CCNRInterrogate.Component.Result.NumRecords) {
-				for (Index = 0; Index < fac->u.CCNRInterrogate.Component.Result.NumRecords; ++Index) {
-					chan_misdn_log(1, bc->port, " -->   CallDetails[%d]:\n", Index);
-					print_facility_CallInformation(3, &fac->u.CCNRInterrogate.Component.Result.CallDetails[Index], bc);
-				}
-			}
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_CCBS_T_Call:
-		chan_misdn_log(1, bc->port, " --> CCBS_T_Call: InvokeID:%d\n",
-			fac->u.CCBS_T_Call.InvokeID);
-		break;
-	case Fac_CCBS_T_Suspend:
-		chan_misdn_log(1, bc->port, " --> CCBS_T_Suspend: InvokeID:%d\n",
-			fac->u.CCBS_T_Suspend.InvokeID);
-		break;
-	case Fac_CCBS_T_Resume:
-		chan_misdn_log(1, bc->port, " --> CCBS_T_Resume: InvokeID:%d\n",
-			fac->u.CCBS_T_Resume.InvokeID);
-		break;
-	case Fac_CCBS_T_RemoteUserFree:
-		chan_misdn_log(1, bc->port, " --> CCBS_T_RemoteUserFree: InvokeID:%d\n",
-			fac->u.CCBS_T_RemoteUserFree.InvokeID);
-		break;
-	case Fac_CCBS_T_Available:
-		chan_misdn_log(1, bc->port, " --> CCBS_T_Available: InvokeID:%d\n",
-			fac->u.CCBS_T_Available.InvokeID);
-		break;
-	case Fac_CCBS_T_Request:
-		chan_misdn_log(1, bc->port, " --> CCBS_T_Request: InvokeID:%d\n",
-			fac->u.CCBS_T_Request.InvokeID);
-		switch (fac->u.CCBS_T_Request.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke\n");
-			chan_misdn_log(1, bc->port, " -->   DestinationAddress:\n");
-			print_facility_Address(3, &fac->u.CCBS_T_Request.Component.Invoke.Destination, bc);
-			print_facility_Q931_Bc_Hlc_Llc(2, &fac->u.CCBS_T_Request.Component.Invoke.Q931ie, bc);
-			if (fac->u.CCBS_T_Request.Component.Invoke.RetentionSupported) {
-				chan_misdn_log(1, bc->port, " -->   RetentionSupported:1\n");
-			}
-			if (fac->u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicatorPresent) {
-				chan_misdn_log(1, bc->port, " -->   PresentationAllowed:%d\n",
-					fac->u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicator);
-			}
-			if (fac->u.CCBS_T_Request.Component.Invoke.Originating.Party.LengthOfNumber) {
-				chan_misdn_log(1, bc->port, " -->   OriginatingAddress:\n");
-				print_facility_Address(3, &fac->u.CCBS_T_Request.Component.Invoke.Originating, bc);
-			}
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result: RetentionSupported:%d\n",
-				fac->u.CCBS_T_Request.Component.Result.RetentionSupported);
-			break;
-		default:
-			break;
-		}
-		break;
-	case Fac_CCNR_T_Request:
-		chan_misdn_log(1, bc->port, " --> CCNR_T_Request: InvokeID:%d\n",
-			fac->u.CCNR_T_Request.InvokeID);
-		switch (fac->u.CCNR_T_Request.ComponentType) {
-		case FacComponent_Invoke:
-			chan_misdn_log(1, bc->port, " -->  Invoke\n");
-			chan_misdn_log(1, bc->port, " -->   DestinationAddress:\n");
-			print_facility_Address(3, &fac->u.CCNR_T_Request.Component.Invoke.Destination, bc);
-			print_facility_Q931_Bc_Hlc_Llc(2, &fac->u.CCNR_T_Request.Component.Invoke.Q931ie, bc);
-			if (fac->u.CCNR_T_Request.Component.Invoke.RetentionSupported) {
-				chan_misdn_log(1, bc->port, " -->   RetentionSupported:1\n");
-			}
-			if (fac->u.CCNR_T_Request.Component.Invoke.PresentationAllowedIndicatorPresent) {
-				chan_misdn_log(1, bc->port, " -->   PresentationAllowed:%d\n",
-					fac->u.CCNR_T_Request.Component.Invoke.PresentationAllowedIndicator);
-			}
-			if (fac->u.CCNR_T_Request.Component.Invoke.Originating.Party.LengthOfNumber) {
-				chan_misdn_log(1, bc->port, " -->   OriginatingAddress:\n");
-				print_facility_Address(3, &fac->u.CCNR_T_Request.Component.Invoke.Originating, bc);
-			}
-			break;
-		case FacComponent_Result:
-			chan_misdn_log(1, bc->port, " -->  Result: RetentionSupported:%d\n",
-				fac->u.CCNR_T_Request.Component.Result.RetentionSupported);
-			break;
-		default:
-			break;
-		}
-		break;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	case Fac_None:
-		/* No facility so print nothing */
-		break;
-	default:
-		chan_misdn_log(1, bc->port, " --> unknown facility\n");
-		break;
-	}
-}
-
-static void print_bearer(struct misdn_bchannel *bc)
-{
-	chan_misdn_log(2, bc->port, " --> Bearer: %s\n", bearer2str(bc->capability));
-
-	switch(bc->law) {
-	case INFO_CODEC_ALAW:
-		chan_misdn_log(2, bc->port, " --> Codec: Alaw\n");
-		break;
-	case INFO_CODEC_ULAW:
-		chan_misdn_log(2, bc->port, " --> Codec: Ulaw\n");
-		break;
-	}
-}
-
-/*!
- * \internal
- * \brief Prefix a string to another string in place.
- *
- * \param str_prefix String to prefix to the main string.
- * \param str_main String to get the prefix added to it.
- * \param size Buffer size of the main string (Includes null terminator).
- *
- * \note The str_main buffer size must be greater than one.
- */
-static void misdn_prefix_string(const char *str_prefix, char *str_main, size_t size)
-{
-	size_t len_over;
-	size_t len_total;
-	size_t len_main;
-	size_t len_prefix;
-
-	len_prefix = strlen(str_prefix);
-	if (!len_prefix) {
-		/* There is no prefix to prepend. */
-		return;
-	}
-	len_main = strlen(str_main);
-	len_total = len_prefix + len_main;
-	if (size <= len_total) {
-		/* We need to truncate since the buffer is too small. */
-		len_over = len_total + 1 - size;
-		if (len_over <= len_main) {
-			len_main -= len_over;
-		} else {
-			len_over -= len_main;
-			len_main = 0;
-			len_prefix -= len_over;
-		}
-	}
-	if (len_main) {
-		memmove(str_main + len_prefix, str_main, len_main);
-	}
-	memcpy(str_main, str_prefix, len_prefix);
-	str_main[len_prefix + len_main] = '\0';
-}
-
-/*!
- * \internal
- * \brief Add a configured prefix to the given number.
- *
- * \param port Logical port number
- * \param number_type Type-of-number passed in.
- * \param number Given number string to add prefix
- * \param size Buffer size number string occupies.
- */
-static void misdn_add_number_prefix(int port, enum mISDN_NUMBER_TYPE number_type, char *number, size_t size)
-{
-	enum misdn_cfg_elements type_prefix;
-	char num_prefix[MISDN_MAX_NUMBER_LEN];
-
-	/* Get prefix string. */
-	switch (number_type) {
-	case NUMTYPE_UNKNOWN:
-		type_prefix = MISDN_CFG_TON_PREFIX_UNKNOWN;
-		break;
-	case NUMTYPE_INTERNATIONAL:
-		type_prefix = MISDN_CFG_TON_PREFIX_INTERNATIONAL;
-		break;
-	case NUMTYPE_NATIONAL:
-		type_prefix = MISDN_CFG_TON_PREFIX_NATIONAL;
-		break;
-	case NUMTYPE_NETWORK_SPECIFIC:
-		type_prefix = MISDN_CFG_TON_PREFIX_NETWORK_SPECIFIC;
-		break;
-	case NUMTYPE_SUBSCRIBER:
-		type_prefix = MISDN_CFG_TON_PREFIX_SUBSCRIBER;
-		break;
-	case NUMTYPE_ABBREVIATED:
-		type_prefix = MISDN_CFG_TON_PREFIX_ABBREVIATED;
-		break;
-	default:
-		/* Type-of-number does not have a prefix that can be added. */
-		return;
-	}
-	misdn_cfg_get(port, type_prefix, num_prefix, sizeof(num_prefix));
-
-	misdn_prefix_string(num_prefix, number, size);
-}
-
-static void export_aoc_vars(int originator, struct ast_channel *ast, struct misdn_bchannel *bc)
-{
-	RAII_VAR(struct ast_channel *, chan, NULL, ast_channel_cleanup);
-	char buf[128];
-
-	if (!bc->AOCD_need_export || !ast) {
-		return;
-	}
-
-	if (originator == ORG_AST) {
-		chan = ast_channel_bridge_peer(ast);
-		if (!chan) {
-			return;
-		}
-	} else {
-		chan = ast_channel_ref(ast);
-	}
-
-	switch (bc->AOCDtype) {
-	case Fac_AOCDCurrency:
-		pbx_builtin_setvar_helper(chan, "AOCD_Type", "currency");
-		if (bc->AOCD.currency.chargeNotAvailable) {
-			pbx_builtin_setvar_helper(chan, "AOCD_ChargeAvailable", "no");
-		} else {
-			pbx_builtin_setvar_helper(chan, "AOCD_ChargeAvailable", "yes");
-			if (bc->AOCD.currency.freeOfCharge) {
-				pbx_builtin_setvar_helper(chan, "AOCD_FreeOfCharge", "yes");
-			} else {
-				pbx_builtin_setvar_helper(chan, "AOCD_FreeOfCharge", "no");
-				if (snprintf(buf, sizeof(buf), "%d %s", bc->AOCD.currency.currencyAmount * bc->AOCD.currency.multiplier, bc->AOCD.currency.currency) < sizeof(buf)) {
-					pbx_builtin_setvar_helper(chan, "AOCD_Amount", buf);
-					if (bc->AOCD.currency.billingId >= 0 && snprintf(buf, sizeof(buf), "%d", bc->AOCD.currency.billingId) < sizeof(buf)) {
-						pbx_builtin_setvar_helper(chan, "AOCD_BillingId", buf);
-					}
-				}
-			}
-		}
-		break;
-	case Fac_AOCDChargingUnit:
-		pbx_builtin_setvar_helper(chan, "AOCD_Type", "charging_unit");
-		if (bc->AOCD.chargingUnit.chargeNotAvailable) {
-			pbx_builtin_setvar_helper(chan, "AOCD_ChargeAvailable", "no");
-		} else {
-			pbx_builtin_setvar_helper(chan, "AOCD_ChargeAvailable", "yes");
-			if (bc->AOCD.chargingUnit.freeOfCharge) {
-				pbx_builtin_setvar_helper(chan, "AOCD_FreeOfCharge", "yes");
-			} else {
-				pbx_builtin_setvar_helper(chan, "AOCD_FreeOfCharge", "no");
-				if (snprintf(buf, sizeof(buf), "%d", bc->AOCD.chargingUnit.recordedUnits) < sizeof(buf)) {
-					pbx_builtin_setvar_helper(chan, "AOCD_RecordedUnits", buf);
-					if (bc->AOCD.chargingUnit.billingId >= 0 && snprintf(buf, sizeof(buf), "%d", bc->AOCD.chargingUnit.billingId) < sizeof(buf)) {
-						pbx_builtin_setvar_helper(chan, "AOCD_BillingId", buf);
-					}
-				}
-			}
-		}
-		break;
-	default:
-		break;
-	}
-
-	bc->AOCD_need_export = 0;
-}
-
-/*************** Helpers END *************/
-
-static void sighandler(int sig)
-{
-}
-
-static void *misdn_tasks_thread_func(void *data)
-{
-	int wait;
-	struct sigaction sa;
-
-	sa.sa_handler = sighandler;
-	sa.sa_flags = SA_NODEFER;
-	sigemptyset(&sa.sa_mask);
-	sigaddset(&sa.sa_mask, SIGUSR1);
-	sigaction(SIGUSR1, &sa, NULL);
-
-	sem_post((sem_t *)data);
-
-	while (1) {
-		wait = ast_sched_wait(misdn_tasks);
-		if (wait < 0) {
-			wait = 8000;
-		}
-		if (poll(NULL, 0, wait) < 0) {
-			chan_misdn_log(4, 0, "Waking up misdn_tasks thread\n");
-		}
-		ast_sched_runq(misdn_tasks);
-	}
-	return NULL;
-}
-
-static void misdn_tasks_init(void)
-{
-	sem_t blocker;
-	int i = 5;
-
-	if (sem_init(&blocker, 0, 0)) {
-		perror("chan_misdn: Failed to initialize semaphore!");
-		exit(1);
-	}
-
-	chan_misdn_log(4, 0, "Starting misdn_tasks thread\n");
-
-	misdn_tasks = ast_sched_context_create();
-	pthread_create(&misdn_tasks_thread, NULL, misdn_tasks_thread_func, &blocker);
-
-	while (sem_wait(&blocker) && --i) {
-	}
-	sem_destroy(&blocker);
-}
-
-static void misdn_tasks_destroy(void)
-{
-	if (misdn_tasks) {
-		chan_misdn_log(4, 0, "Killing misdn_tasks thread\n");
-		if (pthread_cancel(misdn_tasks_thread) == 0) {
-			cb_log(4, 0, "Joining misdn_tasks thread\n");
-			pthread_join(misdn_tasks_thread, NULL);
-		}
-		ast_sched_context_destroy(misdn_tasks);
-	}
-}
-
-static inline void misdn_tasks_wakeup(void)
-{
-	pthread_kill(misdn_tasks_thread, SIGUSR1);
-}
-
-static inline int _misdn_tasks_add_variable(int timeout, ast_sched_cb callback, const void *data, int variable)
-{
-	int task_id;
-
-	if (!misdn_tasks) {
-		misdn_tasks_init();
-	}
-	task_id = ast_sched_add_variable(misdn_tasks, timeout, callback, data, variable);
-	misdn_tasks_wakeup();
-
-	return task_id;
-}
-
-static int misdn_tasks_add(int timeout, ast_sched_cb callback, const void *data)
-{
-	return _misdn_tasks_add_variable(timeout, callback, data, 0);
-}
-
-static int misdn_tasks_add_variable(int timeout, ast_sched_cb callback, const void *data)
-{
-	return _misdn_tasks_add_variable(timeout, callback, data, 1);
-}
-
-static void misdn_tasks_remove(int task_id)
-{
-	AST_SCHED_DEL(misdn_tasks, task_id);
-}
-
-static int misdn_l1_task(const void *vdata)
-{
-	const int *data = vdata;
-
-	misdn_lib_isdn_l1watcher(*data);
-	chan_misdn_log(5, *data, "L1watcher timeout\n");
-	return 1;
-}
-
-static int misdn_overlap_dial_task(const void *data)
-{
-	struct timeval tv_end, tv_now;
-	int diff;
-	struct chan_list *ch = (struct chan_list *) data;
-	char *dad;
-
-	chan_misdn_log(4, ch->bc->port, "overlap dial task, chan_state: %d\n", ch->state);
-
-	if (ch->state != MISDN_WAITING4DIGS) {
-		ch->overlap_dial_task = -1;
-		return 0;
-	}
-
-	ast_mutex_lock(&ch->overlap_tv_lock);
-	tv_end = ch->overlap_tv;
-	ast_mutex_unlock(&ch->overlap_tv_lock);
-
-	tv_end.tv_sec += ch->overlap_dial;
-	tv_now = ast_tvnow();
-
-	diff = ast_tvdiff_ms(tv_end, tv_now);
-	if (100 < diff) {
-		return diff;
-	}
-
-	/* if we are 100ms near the timeout, we are satisfied.. */
-	stop_indicate(ch);
-
-	if (ast_strlen_zero(ch->bc->dialed.number)) {
-		dad = "s";
-		ast_channel_exten_set(ch->ast, dad);
-	} else {
-		dad = ch->bc->dialed.number;
-	}
-
-	if (ast_exists_extension(ch->ast, ch->context, dad, 1, ch->bc->caller.number)) {
-		ch->state = MISDN_DIALING;
-		if (pbx_start_chan(ch) < 0) {
-			chan_misdn_log(-1, ch->bc->port, "ast_pbx_start returned < 0 in misdn_overlap_dial_task\n");
-			goto misdn_overlap_dial_task_disconnect;
-		}
-	} else {
-misdn_overlap_dial_task_disconnect:
-		hanguptone_indicate(ch);
-		ch->bc->out_cause = AST_CAUSE_UNALLOCATED;
-		ch->state = MISDN_CLEANING;
-		misdn_lib_send_event(ch->bc, EVENT_DISCONNECT);
-	}
-	ch->overlap_dial_task = -1;
-	return 0;
-}
-
-static void send_digit_to_chan(struct chan_list *cl, char digit)
-{
-	static const char * const dtmf_tones[] = {
-/* *INDENT-OFF* */
-		"!941+1336/100,!0/100",	/* 0 */
-		"!697+1209/100,!0/100",	/* 1 */
-		"!697+1336/100,!0/100",	/* 2 */
-		"!697+1477/100,!0/100",	/* 3 */
-		"!770+1209/100,!0/100",	/* 4 */
-		"!770+1336/100,!0/100",	/* 5 */
-		"!770+1477/100,!0/100",	/* 6 */
-		"!852+1209/100,!0/100",	/* 7 */
-		"!852+1336/100,!0/100",	/* 8 */
-		"!852+1477/100,!0/100",	/* 9 */
-		"!697+1633/100,!0/100",	/* A */
-		"!770+1633/100,!0/100",	/* B */
-		"!852+1633/100,!0/100",	/* C */
-		"!941+1633/100,!0/100",	/* D */
-		"!941+1209/100,!0/100",	/* * */
-		"!941+1477/100,!0/100", /* # */
-/* *INDENT-ON* */
-	};
-	struct ast_channel *chan = cl->ast;
-
-	if (digit >= '0' && digit <='9') {
-		ast_playtones_start(chan, 0, dtmf_tones[digit - '0'], 0);
-	} else if (digit >= 'A' && digit <= 'D') {
-		ast_playtones_start(chan, 0, dtmf_tones[digit - 'A' + 10], 0);
-	} else if (digit == '*') {
-		ast_playtones_start(chan, 0, dtmf_tones[14], 0);
-	} else if (digit == '#') {
-		ast_playtones_start(chan, 0, dtmf_tones[15], 0);
-	} else {
-		/* not handled */
-		ast_debug(1, "Unable to handle DTMF tone '%c' for '%s'\n", digit, ast_channel_name(chan));
-	}
-}
-
-/*** CLI HANDLING ***/
-static char *handle_cli_misdn_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	int level;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn set debug [on|off]";
-		e->usage =
-			"Usage: misdn set debug {on|off|<level>} [only] | [port <port> [only]]\n"
-			"       Set the debug level of the mISDN channel.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return complete_debug_port(a);
-	}
-
-	if (a->argc < 4 || a->argc > 7) {
-		return CLI_SHOWUSAGE;
-	}
-
-	if (!strcasecmp(a->argv[3], "on")) {
-		level = 1;
-	} else if (!strcasecmp(a->argv[3], "off")) {
-		level = 0;
-	} else if (isdigit(a->argv[3][0])) {
-		level = atoi(a->argv[3]);
-	} else {
-		return CLI_SHOWUSAGE;
-	}
-
-	switch (a->argc) {
-	case 4:
-	case 5:
-		{
-			int i;
-			int only = 0;
-			if (a->argc == 5) {
-				if (strncasecmp(a->argv[4], "only", strlen(a->argv[4]))) {
-					return CLI_SHOWUSAGE;
-				} else {
-					only = 1;
-				}
-			}
-
-			for (i = 0; i <= max_ports; i++) {
-				misdn_debug[i] = level;
-				misdn_debug_only[i] = only;
-			}
-			ast_cli(a->fd, "changing debug level for all ports to %d%s\n", misdn_debug[0], only ? " (only)" : "");
-		}
-		break;
-	case 6:
-	case 7:
-		{
-			int port;
-			if (strncasecmp(a->argv[4], "port", strlen(a->argv[4])))
-				return CLI_SHOWUSAGE;
-			port = atoi(a->argv[5]);
-			if (port <= 0 || port > max_ports) {
-				switch (max_ports) {
-				case 0:
-					ast_cli(a->fd, "port number not valid! no ports available so you won't get lucky with any number here...\n");
-					break;
-				case 1:
-					ast_cli(a->fd, "port number not valid! only port 1 is available.\n");
-					break;
-				default:
-					ast_cli(a->fd, "port number not valid! only ports 1 to %d are available.\n", max_ports);
-				}
-				return 0;
-			}
-			if (a->argc == 7) {
-				if (strncasecmp(a->argv[6], "only", strlen(a->argv[6]))) {
-					return CLI_SHOWUSAGE;
-				} else {
-					misdn_debug_only[port] = 1;
-				}
-			} else {
-				misdn_debug_only[port] = 0;
-			}
-			misdn_debug[port] = level;
-			ast_cli(a->fd, "changing debug level to %d%s for port %d\n", misdn_debug[port], misdn_debug_only[port] ? " (only)" : "", port);
-		}
-	}
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_set_crypt_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn set crypt debug";
-		e->usage =
-			"Usage: misdn set crypt debug <level>\n"
-			"       Set the crypt debug level of the mISDN channel. Level\n"
-			"       must be 1 or 2.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 5) {
-		return CLI_SHOWUSAGE;
-	}
-
-	/* XXX Is this supposed to not do anything? XXX */
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_port_block(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn port block";
-		e->usage =
-			"Usage: misdn port block <port>\n"
-			"       Block the specified port by <port>.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 4) {
-		return CLI_SHOWUSAGE;
-	}
-
-	misdn_lib_port_block(atoi(a->argv[3]));
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_port_unblock(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn port unblock";
-		e->usage =
-			"Usage: misdn port unblock <port>\n"
-			"       Unblock the port specified by <port>.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 4) {
-		return CLI_SHOWUSAGE;
-	}
-
-	misdn_lib_port_unblock(atoi(a->argv[3]));
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_restart_port(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn restart port";
-		e->usage =
-			"Usage: misdn restart port <port>\n"
-			"       Restart the given port.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 4) {
-		return CLI_SHOWUSAGE;
-	}
-
-	misdn_lib_port_restart(atoi(a->argv[3]));
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_restart_pid(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn restart pid";
-		e->usage =
-			"Usage: misdn restart pid <pid>\n"
-			"       Restart the given pid\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 4) {
-		return CLI_SHOWUSAGE;
-	}
-
-	misdn_lib_pid_restart(atoi(a->argv[3]));
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_port_up(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn port up";
-		e->usage =
-			"Usage: misdn port up <port>\n"
-			"       Try to establish L1 on the given port.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 4) {
-		return CLI_SHOWUSAGE;
-	}
-
-	misdn_lib_get_port_up(atoi(a->argv[3]));
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_port_down(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn port down";
-		e->usage =
-			"Usage: misdn port down <port>\n"
-			"       Try to deactivate the L1 on the given port.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 4) {
-		return CLI_SHOWUSAGE;
-	}
-
-	misdn_lib_get_port_down(atoi(a->argv[3]));
-
-	return CLI_SUCCESS;
-}
-
-static inline void show_config_description(int fd, enum misdn_cfg_elements elem)
-{
-	char section[BUFFERSIZE];
-	char name[BUFFERSIZE];
-	char desc[BUFFERSIZE];
-	char def[BUFFERSIZE];
-	char tmp[BUFFERSIZE];
-
-	misdn_cfg_get_name(elem, tmp, sizeof(tmp));
-	term_color(name, tmp, COLOR_BRWHITE, 0, sizeof(tmp));
-	misdn_cfg_get_desc(elem, desc, sizeof(desc), def, sizeof(def));
-
-	if (elem < MISDN_CFG_LAST) {
-		term_color(section, "PORTS SECTION", COLOR_YELLOW, 0, sizeof(section));
-	} else {
-		term_color(section, "GENERAL SECTION", COLOR_YELLOW, 0, sizeof(section));
-	}
-
-	if (*def) {
-		ast_cli(fd, "[%s] %s   (Default: %s)\n\t%s\n", section, name, def, desc);
-	} else {
-		ast_cli(fd, "[%s] %s\n\t%s\n", section, name, desc);
-	}
-}
-
-static char *handle_cli_misdn_show_config(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	char buffer[BUFFERSIZE];
-	enum misdn_cfg_elements elem;
-	int linebreak;
-	int onlyport = -1;
-	int ok = 0;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn show config";
-		e->usage =
-			"Usage: misdn show config [<port> | description <config element> | descriptions [general|ports]]\n"
-			"       Use 0 for <port> to only print the general config.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return complete_show_config(a);
-	}
-
-	if (a->argc >= 4) {
-		if (!strcmp(a->argv[3], "description")) {
-			if (a->argc == 5) {
-				enum misdn_cfg_elements elem = misdn_cfg_get_elem(a->argv[4]);
-				if (elem == MISDN_CFG_FIRST) {
-					ast_cli(a->fd, "Unknown element: %s\n", a->argv[4]);
-				} else {
-					show_config_description(a->fd, elem);
-				}
-				return CLI_SUCCESS;
-			}
-			return CLI_SHOWUSAGE;
-		} else if (!strcmp(a->argv[3], "descriptions")) {
-			if ((a->argc == 4) || ((a->argc == 5) && !strcmp(a->argv[4], "general"))) {
-				for (elem = MISDN_GEN_FIRST + 1; elem < MISDN_GEN_LAST; ++elem) {
-					show_config_description(a->fd, elem);
-					ast_cli(a->fd, "\n");
-				}
-				ok = 1;
-			}
-			if ((a->argc == 4) || ((a->argc == 5) && !strcmp(a->argv[4], "ports"))) {
-				for (elem = MISDN_CFG_FIRST + 1; elem < MISDN_CFG_LAST - 1 /* the ptp hack, remove the -1 when ptp is gone */; ++elem) {
-					show_config_description(a->fd, elem);
-					ast_cli(a->fd, "\n");
-				}
-				ok = 1;
-			}
-			return ok ? CLI_SUCCESS : CLI_SHOWUSAGE;
-		} else if (!sscanf(a->argv[3], "%5d", &onlyport) || onlyport < 0) {
-			ast_cli(a->fd, "Unknown option: %s\n", a->argv[3]);
-			return CLI_SHOWUSAGE;
-		}
-	}
-
-	if (a->argc == 3 || onlyport == 0) {
-		ast_cli(a->fd, "mISDN General-Config:\n");
-		for (elem = MISDN_GEN_FIRST + 1, linebreak = 1; elem < MISDN_GEN_LAST; elem++, linebreak++) {
-			misdn_cfg_get_config_string(0, elem, buffer, sizeof(buffer));
-			ast_cli(a->fd, "%-36s%s", buffer, !(linebreak % 2) ? "\n" : "");
-		}
-		ast_cli(a->fd, "\n");
-	}
-
-	if (onlyport < 0) {
-		int port = misdn_cfg_get_next_port(0);
-
-		for (; port > 0; port = misdn_cfg_get_next_port(port)) {
-			ast_cli(a->fd, "\n[PORT %d]\n", port);
-			for (elem = MISDN_CFG_FIRST + 1, linebreak = 1; elem < MISDN_CFG_LAST; elem++, linebreak++) {
-				misdn_cfg_get_config_string(port, elem, buffer, sizeof(buffer));
-				ast_cli(a->fd, "%-36s%s", buffer, !(linebreak % 2) ? "\n" : "");
-			}
-			ast_cli(a->fd, "\n");
-		}
-	}
-
-	if (onlyport > 0) {
-		if (misdn_cfg_is_port_valid(onlyport)) {
-			ast_cli(a->fd, "[PORT %d]\n", onlyport);
-			for (elem = MISDN_CFG_FIRST + 1, linebreak = 1; elem < MISDN_CFG_LAST; elem++, linebreak++) {
-				misdn_cfg_get_config_string(onlyport, elem, buffer, sizeof(buffer));
-				ast_cli(a->fd, "%-36s%s", buffer, !(linebreak % 2) ? "\n" : "");
-			}
-			ast_cli(a->fd, "\n");
-		} else {
-			ast_cli(a->fd, "Port %d is not active!\n", onlyport);
-		}
-	}
-
-	return CLI_SUCCESS;
-}
-
-struct state_struct {
-	enum misdn_chan_state state;
-	char txt[255];
-};
-
-static const struct state_struct state_array[] = {
-/* *INDENT-OFF* */
-	{ MISDN_NOTHING,             "NOTHING" },             /* at beginning */
-	{ MISDN_WAITING4DIGS,        "WAITING4DIGS" },        /* when waiting for infos */
-	{ MISDN_EXTCANTMATCH,        "EXTCANTMATCH" },        /* when asterisk couldn't match our ext */
-	{ MISDN_INCOMING_SETUP,      "INCOMING SETUP" },      /* when pbx_start */
-	{ MISDN_DIALING,             "DIALING" },             /* when pbx_start */
-	{ MISDN_PROGRESS,            "PROGRESS" },            /* when pbx_start */
-	{ MISDN_PROCEEDING,          "PROCEEDING" },          /* when pbx_start */
-	{ MISDN_CALLING,             "CALLING" },             /* when misdn_call is called */
-	{ MISDN_CALLING_ACKNOWLEDGE, "CALLING_ACKNOWLEDGE" }, /* when misdn_call is called */
-	{ MISDN_ALERTING,            "ALERTING" },            /* when Alerting */
-	{ MISDN_BUSY,                "BUSY" },                /* when BUSY */
-	{ MISDN_CONNECTED,           "CONNECTED" },           /* when connected */
-	{ MISDN_DISCONNECTED,        "DISCONNECTED" },        /* when connected */
-	{ MISDN_CLEANING,            "CLEANING" },            /* when hangup from * but we were connected before */
-/* *INDENT-ON* */
-};
-
-static const char *misdn_get_ch_state(struct chan_list *p)
-{
-	int i;
-	static char state[8];
-
-	if (!p) {
-		return NULL;
-	}
-
-	for (i = 0; i < ARRAY_LEN(state_array); i++) {
-		if (state_array[i].state == p->state) {
-			return state_array[i].txt;
-		}
-	}
-
- 	snprintf(state, sizeof(state), "%d", p->state) ;
-
-	return state;
-}
-
-
-static void reload_config(void)
-{
-	int i, cfg_debug;
-
-	if (!g_config_initialized) {
-		ast_log(LOG_WARNING, "chan_misdn is not initialized properly, still reloading ?\n");
-		return ;
-	}
-
-	free_robin_list();
-	misdn_cfg_reload();
-	misdn_cfg_update_ptp();
-	misdn_cfg_get(0, MISDN_GEN_TRACEFILE, global_tracefile, sizeof(global_tracefile));
-	misdn_cfg_get(0, MISDN_GEN_DEBUG, &cfg_debug, sizeof(cfg_debug));
-
-	for (i = 0;  i <= max_ports; i++) {
-		misdn_debug[i] = cfg_debug;
-		misdn_debug_only[i] = 0;
-	}
-}
-
-static char *handle_cli_misdn_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn reload";
-		e->usage =
-			"Usage: misdn reload\n"
-			"       Reload internal mISDN config, read from the config\n"
-			"       file.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 2) {
-		return CLI_SHOWUSAGE;
-	}
-
-	ast_cli(a->fd, "Reloading mISDN configuration\n");
-	reload_config();
-	return CLI_SUCCESS;
-}
-
-static void print_bc_info(int fd, struct chan_list *help, struct misdn_bchannel *bc)
-{
-	struct ast_channel *ast = help->ast;
-
-	ast_cli(fd,
-		"* Pid:%d Port:%d Ch:%d Mode:%s Orig:%s dialed:%s\n"
-		"  --> caller:\"%s\" <%s>\n"
-		"  --> redirecting-from:\"%s\" <%s>\n"
-		"  --> redirecting-to:\"%s\" <%s>\n"
-		"  --> context:%s state:%s\n",
-		bc->pid,
-		bc->port,
-		bc->channel,
-		bc->nt ? "NT" : "TE",
-		help->originator == ORG_AST ? "*" : "I",
-		ast ? ast_channel_exten(ast) : "",
-		(ast && ast_channel_caller(ast)->id.name.valid && ast_channel_caller(ast)->id.name.str)
-			? ast_channel_caller(ast)->id.name.str : "",
-		(ast && ast_channel_caller(ast)->id.number.valid && ast_channel_caller(ast)->id.number.str)
-			? ast_channel_caller(ast)->id.number.str : "",
-		bc->redirecting.from.name,
-		bc->redirecting.from.number,
-		bc->redirecting.to.name,
-		bc->redirecting.to.number,
-		ast ? ast_channel_context(ast) : "",
-		misdn_get_ch_state(help));
-	if (misdn_debug[bc->port] > 0) {
-		ast_cli(fd,
-			"  --> astname: %s\n"
-			"  --> ch_l3id: %x\n"
-			"  --> ch_addr: %x\n"
-			"  --> bc_addr: %x\n"
-			"  --> bc_l3id: %x\n"
-			"  --> display: %s\n"
-			"  --> activated: %d\n"
-			"  --> state: %s\n"
-			"  --> capability: %s\n"
-#ifdef MISDN_1_2
-			"  --> pipeline: %s\n"
-#else
-			"  --> echo_cancel: %d\n"
-#endif
-			"  --> notone : rx %d tx:%d\n"
-			"  --> bc_hold: %d\n",
-			ast ? ast_channel_name(ast) : "",
-			help->l3id,
-			help->addr,
-			bc->addr,
-			bc->l3_id,
-			bc->display,
-			bc->active,
-			bc_state2str(bc->bc_state),
-			bearer2str(bc->capability),
-#ifdef MISDN_1_2
-			bc->pipeline,
-#else
-			bc->ec_enable,
-#endif
-			help->norxtone, help->notxtone,
-			bc->holded);
-	}
-}
-
-static char *handle_cli_misdn_show_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct chan_list *help;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn show channels";
-		e->usage =
-			"Usage: misdn show channels\n"
-			"       Show the internal mISDN channel list\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 3) {
-		return CLI_SHOWUSAGE;
-	}
-
-	ast_cli(a->fd, "Channel List: %p\n", cl_te);
-
-	/*
-	 * Walking the list and dumping the channel information could
-	 * take awhile.  With the list locked for the duration, the
-	 * channel driver cannot process signaling messages.  However,
-	 * since this is a CLI command it should not be run very often.
-	 */
-	ast_mutex_lock(&cl_te_lock);
-	for (help = cl_te; help; help = help->next) {
-		struct misdn_bchannel *bc = help->bc;
-		struct ast_channel *ast = help->ast;
-		if (!ast) {
-			if (!bc) {
-				ast_cli(a->fd, "chan_list obj. with l3id:%x has no bc and no ast Leg\n", help->l3id);
-				continue;
-			}
-			ast_cli(a->fd, "bc with pid:%d has no Ast Leg\n", bc->pid);
-		}
-
-		if (misdn_debug[0] > 2) {
-			ast_cli(a->fd, "Bc:%p Ast:%p\n", bc, ast);
-		}
-		if (bc) {
-			print_bc_info(a->fd, help, bc);
-		} else {
-			if (help->hold.state != MISDN_HOLD_IDLE) {
-				ast_cli(a->fd, "ITS A HELD CALL BC:\n");
-				ast_cli(a->fd, " --> l3_id: %x\n"
-					" --> dialed:%s\n"
-					" --> caller:\"%s\" <%s>\n"
-					" --> hold_port: %d\n"
-					" --> hold_channel: %d\n",
-					help->l3id,
-					ast_channel_exten(ast),
-					S_COR(ast_channel_caller(ast)->id.name.valid, ast_channel_caller(ast)->id.name.str, ""),
-					S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, ""),
-					help->hold.port,
-					help->hold.channel
-					);
-			} else {
-				ast_cli(a->fd, "* Channel in unknown STATE !!! Exten:%s, Callerid:%s\n",
-					ast_channel_exten(ast),
-					S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, ""));
-			}
-		}
-	}
-	ast_mutex_unlock(&cl_te_lock);
-
- 	misdn_dump_chanlist();
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_show_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct chan_list *help;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn show channel";
-		e->usage =
-			"Usage: misdn show channel <channel>\n"
-			"       Show an internal mISDN channel\n.";
-		return NULL;
-	case CLI_GENERATE:
-		return complete_ch(a);
-	}
-
-	if (a->argc != 4) {
-		return CLI_SHOWUSAGE;
-	}
-
-	ast_mutex_lock(&cl_te_lock);
-	for (help = cl_te; help; help = help->next) {
-		struct misdn_bchannel *bc = help->bc;
-		struct ast_channel *ast = help->ast;
-
-		if (bc && ast) {
-			if (!strcasecmp(ast_channel_name(ast), a->argv[3])) {
-				print_bc_info(a->fd, help, bc);
-				break;
-			}
-		}
-	}
-	ast_mutex_unlock(&cl_te_lock);
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_set_tics(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn set tics";
-		e->usage =
-			"Usage: misdn set tics <value>\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 4) {
-		return CLI_SHOWUSAGE;
-	}
-
-	/* XXX Wow, this does... a whole lot of nothing... XXX */
-	MAXTICS = atoi(a->argv[3]);
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_show_stacks(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	int port;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn show stacks";
-		e->usage =
-			"Usage: misdn show stacks\n"
-			"       Show internal mISDN stack_list.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 3) {
-		return CLI_SHOWUSAGE;
-	}
-
-	ast_cli(a->fd, "BEGIN STACK_LIST:\n");
-	for (port = misdn_cfg_get_next_port(0); port > 0;
-		port = misdn_cfg_get_next_port(port)) {
-		char buf[128];
-
-		get_show_stack_details(port, buf);
-		ast_cli(a->fd, "  %s  Debug:%d%s\n", buf, misdn_debug[port], misdn_debug_only[port] ? "(only)" : "");
-	}
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_show_ports_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	int port;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn show ports stats";
-		e->usage =
-			"Usage: misdn show ports stats\n"
-			"       Show mISDNs channel's call statistics per port.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 4) {
-		return CLI_SHOWUSAGE;
-	}
-
-	ast_cli(a->fd, "Port\tin_calls\tout_calls\n");
-	for (port = misdn_cfg_get_next_port(0); port > 0;
-		port = misdn_cfg_get_next_port(port)) {
-		ast_cli(a->fd, "%d\t%d\t\t%d\n", port, misdn_in_calls[port], misdn_out_calls[port]);
-	}
-	ast_cli(a->fd, "\n");
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_show_port(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	int port;
-	char buf[128];
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn show port";
-		e->usage =
-			"Usage: misdn show port <port>\n"
-			"       Show detailed information for given port.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 4) {
-		return CLI_SHOWUSAGE;
-	}
-
-	port = atoi(a->argv[3]);
-
-	ast_cli(a->fd, "BEGIN STACK_LIST:\n");
-	get_show_stack_details(port, buf);
-	ast_cli(a->fd, "  %s  Debug:%d%s\n", buf, misdn_debug[port], misdn_debug_only[port] ? "(only)" : "");
-
-	return CLI_SUCCESS;
-}
-
-#if defined(AST_MISDN_ENHANCEMENTS) && defined(CCBS_TEST_MESSAGES)
-static const struct FacParm Fac_Msgs[] = {
-/* *INDENT-OFF* */
-	[0].Function = Fac_ERROR,
-	[0].u.ERROR.invokeId = 8,
-	[0].u.ERROR.errorValue = FacError_CCBS_AlreadyAccepted,
-
-	[1].Function = Fac_RESULT,
-	[1].u.RESULT.InvokeID = 9,
-
-	[2].Function = Fac_REJECT,
-	[2].u.REJECT.Code = FacReject_Gen_BadlyStructuredComponent,
-
-	[3].Function = Fac_REJECT,
-	[3].u.REJECT.InvokeIDPresent = 1,
-	[3].u.REJECT.InvokeID = 10,
-	[3].u.REJECT.Code = FacReject_Inv_InitiatorReleasing,
-
-	[4].Function = Fac_REJECT,
-	[4].u.REJECT.InvokeIDPresent = 1,
-	[4].u.REJECT.InvokeID = 11,
-	[4].u.REJECT.Code = FacReject_Res_MistypedResult,
-
-	[5].Function = Fac_REJECT,
-	[5].u.REJECT.InvokeIDPresent = 1,
-	[5].u.REJECT.InvokeID = 12,
-	[5].u.REJECT.Code = FacReject_Err_ErrorResponseUnexpected,
-
-	[6].Function = Fac_StatusRequest,
-	[6].u.StatusRequest.InvokeID = 13,
-	[6].u.StatusRequest.ComponentType = FacComponent_Invoke,
-	[6].u.StatusRequest.Component.Invoke.Q931ie.Bc.Length = 2,
-	[6].u.StatusRequest.Component.Invoke.Q931ie.Bc.Contents = "AB",
-	[6].u.StatusRequest.Component.Invoke.Q931ie.Llc.Length = 3,
-	[6].u.StatusRequest.Component.Invoke.Q931ie.Llc.Contents = "CDE",
-	[6].u.StatusRequest.Component.Invoke.Q931ie.Hlc.Length = 4,
-	[6].u.StatusRequest.Component.Invoke.Q931ie.Hlc.Contents = "FGHI",
-	[6].u.StatusRequest.Component.Invoke.CompatibilityMode = 1,
-
-	[7].Function = Fac_StatusRequest,
-	[7].u.StatusRequest.InvokeID = 14,
-	[7].u.StatusRequest.ComponentType = FacComponent_Result,
-	[7].u.StatusRequest.Component.Result.Status = 2,
-
-	[8].Function = Fac_CallInfoRetain,
-	[8].u.CallInfoRetain.InvokeID = 15,
-	[8].u.CallInfoRetain.CallLinkageID = 115,
-
-	[9].Function = Fac_EraseCallLinkageID,
-	[9].u.EraseCallLinkageID.InvokeID = 16,
-	[9].u.EraseCallLinkageID.CallLinkageID = 105,
-
-	[10].Function = Fac_CCBSDeactivate,
-	[10].u.CCBSDeactivate.InvokeID = 17,
-	[10].u.CCBSDeactivate.ComponentType = FacComponent_Invoke,
-	[10].u.CCBSDeactivate.Component.Invoke.CCBSReference = 2,
-
-	[11].Function = Fac_CCBSDeactivate,
-	[11].u.CCBSDeactivate.InvokeID = 18,
-	[11].u.CCBSDeactivate.ComponentType = FacComponent_Result,
-
-	[12].Function = Fac_CCBSErase,
-	[12].u.CCBSErase.InvokeID = 19,
-	[12].u.CCBSErase.Q931ie.Bc.Length = 2,
-	[12].u.CCBSErase.Q931ie.Bc.Contents = "JK",
-	[12].u.CCBSErase.AddressOfB.Party.Type = 0,
-	[12].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 5,
-	[12].u.CCBSErase.AddressOfB.Party.Number = "33403",
-	[12].u.CCBSErase.AddressOfB.Subaddress.Type = 0,
-	[12].u.CCBSErase.AddressOfB.Subaddress.Length = 4,
-	[12].u.CCBSErase.AddressOfB.Subaddress.u.UserSpecified.Information = "3748",
-	[12].u.CCBSErase.RecallMode = 1,
-	[12].u.CCBSErase.CCBSReference = 102,
-	[12].u.CCBSErase.Reason = 3,
-
-	[13].Function = Fac_CCBSErase,
-	[13].u.CCBSErase.InvokeID = 20,
-	[13].u.CCBSErase.Q931ie.Bc.Length = 2,
-	[13].u.CCBSErase.Q931ie.Bc.Contents = "JK",
-	[13].u.CCBSErase.AddressOfB.Party.Type = 1,
-	[13].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 11,
-	[13].u.CCBSErase.AddressOfB.Party.TypeOfNumber = 1,
-	[13].u.CCBSErase.AddressOfB.Party.Number = "18003020102",
-	[13].u.CCBSErase.AddressOfB.Subaddress.Type = 0,
-	[13].u.CCBSErase.AddressOfB.Subaddress.Length = 4,
-	[13].u.CCBSErase.AddressOfB.Subaddress.u.UserSpecified.OddCountPresent = 1,
-	[13].u.CCBSErase.AddressOfB.Subaddress.u.UserSpecified.OddCount = 1,
-	[13].u.CCBSErase.AddressOfB.Subaddress.u.UserSpecified.Information = "3748",
-	[13].u.CCBSErase.RecallMode = 1,
-	[13].u.CCBSErase.CCBSReference = 102,
-	[13].u.CCBSErase.Reason = 3,
-
-	[14].Function = Fac_CCBSErase,
-	[14].u.CCBSErase.InvokeID = 21,
-	[14].u.CCBSErase.Q931ie.Bc.Length = 2,
-	[14].u.CCBSErase.Q931ie.Bc.Contents = "JK",
-	[14].u.CCBSErase.AddressOfB.Party.Type = 2,
-	[14].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 4,
-	[14].u.CCBSErase.AddressOfB.Party.Number = "1803",
-	[14].u.CCBSErase.AddressOfB.Subaddress.Type = 1,
-	[14].u.CCBSErase.AddressOfB.Subaddress.Length = 4,
-	[14].u.CCBSErase.AddressOfB.Subaddress.u.Nsap = "6492",
-	[14].u.CCBSErase.RecallMode = 1,
-	[14].u.CCBSErase.CCBSReference = 102,
-	[14].u.CCBSErase.Reason = 3,
-
-	[15].Function = Fac_CCBSErase,
-	[15].u.CCBSErase.InvokeID = 22,
-	[15].u.CCBSErase.Q931ie.Bc.Length = 2,
-	[15].u.CCBSErase.Q931ie.Bc.Contents = "JK",
-	[15].u.CCBSErase.AddressOfB.Party.Type = 3,
-	[15].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 4,
-	[15].u.CCBSErase.AddressOfB.Party.Number = "1803",
-	[15].u.CCBSErase.RecallMode = 1,
-	[15].u.CCBSErase.CCBSReference = 102,
-	[15].u.CCBSErase.Reason = 3,
-
-	[16].Function = Fac_CCBSErase,
-	[16].u.CCBSErase.InvokeID = 23,
-	[16].u.CCBSErase.Q931ie.Bc.Length = 2,
-	[16].u.CCBSErase.Q931ie.Bc.Contents = "JK",
-	[16].u.CCBSErase.AddressOfB.Party.Type = 4,
-	[16].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 4,
-	[16].u.CCBSErase.AddressOfB.Party.Number = "1803",
-	[16].u.CCBSErase.RecallMode = 1,
-	[16].u.CCBSErase.CCBSReference = 102,
-	[16].u.CCBSErase.Reason = 3,
-
-	[17].Function = Fac_CCBSErase,
-	[17].u.CCBSErase.InvokeID = 24,
-	[17].u.CCBSErase.Q931ie.Bc.Length = 2,
-	[17].u.CCBSErase.Q931ie.Bc.Contents = "JK",
-	[17].u.CCBSErase.AddressOfB.Party.Type = 5,
-	[17].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 11,
-	[17].u.CCBSErase.AddressOfB.Party.TypeOfNumber = 4,
-	[17].u.CCBSErase.AddressOfB.Party.Number = "18003020102",
-	[17].u.CCBSErase.RecallMode = 1,
-	[17].u.CCBSErase.CCBSReference = 102,
-	[17].u.CCBSErase.Reason = 3,
-
-	[18].Function = Fac_CCBSErase,
-	[18].u.CCBSErase.InvokeID = 25,
-	[18].u.CCBSErase.Q931ie.Bc.Length = 2,
-	[18].u.CCBSErase.Q931ie.Bc.Contents = "JK",
-	[18].u.CCBSErase.AddressOfB.Party.Type = 8,
-	[18].u.CCBSErase.AddressOfB.Party.LengthOfNumber = 4,
-	[18].u.CCBSErase.AddressOfB.Party.Number = "1803",
-	[18].u.CCBSErase.RecallMode = 1,
-	[18].u.CCBSErase.CCBSReference = 102,
-	[18].u.CCBSErase.Reason = 3,
-
-	[19].Function = Fac_CCBSRemoteUserFree,
-	[19].u.CCBSRemoteUserFree.InvokeID = 26,
-	[19].u.CCBSRemoteUserFree.Q931ie.Bc.Length = 2,
-	[19].u.CCBSRemoteUserFree.Q931ie.Bc.Contents = "JK",
-	[19].u.CCBSRemoteUserFree.AddressOfB.Party.Type = 8,
-	[19].u.CCBSRemoteUserFree.AddressOfB.Party.LengthOfNumber = 4,
-	[19].u.CCBSRemoteUserFree.AddressOfB.Party.Number = "1803",
-	[19].u.CCBSRemoteUserFree.RecallMode = 1,
-	[19].u.CCBSRemoteUserFree.CCBSReference = 102,
-
-	[20].Function = Fac_CCBSCall,
-	[20].u.CCBSCall.InvokeID = 27,
-	[20].u.CCBSCall.CCBSReference = 115,
-
-	[21].Function = Fac_CCBSStatusRequest,
-	[21].u.CCBSStatusRequest.InvokeID = 28,
-	[21].u.CCBSStatusRequest.ComponentType = FacComponent_Invoke,
-	[21].u.CCBSStatusRequest.Component.Invoke.Q931ie.Bc.Length = 2,
-	[21].u.CCBSStatusRequest.Component.Invoke.Q931ie.Bc.Contents = "JK",
-	[21].u.CCBSStatusRequest.Component.Invoke.RecallMode = 1,
-	[21].u.CCBSStatusRequest.Component.Invoke.CCBSReference = 102,
-
-	[22].Function = Fac_CCBSStatusRequest,
-	[22].u.CCBSStatusRequest.InvokeID = 29,
-	[22].u.CCBSStatusRequest.ComponentType = FacComponent_Result,
-	[22].u.CCBSStatusRequest.Component.Result.Free = 1,
-
-	[23].Function = Fac_CCBSBFree,
-	[23].u.CCBSBFree.InvokeID = 30,
-	[23].u.CCBSBFree.Q931ie.Bc.Length = 2,
-	[23].u.CCBSBFree.Q931ie.Bc.Contents = "JK",
-	[23].u.CCBSBFree.AddressOfB.Party.Type = 8,
-	[23].u.CCBSBFree.AddressOfB.Party.LengthOfNumber = 4,
-	[23].u.CCBSBFree.AddressOfB.Party.Number = "1803",
-	[23].u.CCBSBFree.RecallMode = 1,
-	[23].u.CCBSBFree.CCBSReference = 14,
-
-	[24].Function = Fac_CCBSStopAlerting,
-	[24].u.CCBSStopAlerting.InvokeID = 31,
-	[24].u.CCBSStopAlerting.CCBSReference = 37,
-
-	[25].Function = Fac_CCBSRequest,
-	[25].u.CCBSRequest.InvokeID = 32,
-	[25].u.CCBSRequest.ComponentType = FacComponent_Invoke,
-	[25].u.CCBSRequest.Component.Invoke.CallLinkageID = 57,
-
-	[26].Function = Fac_CCBSRequest,
-	[26].u.CCBSRequest.InvokeID = 33,
-	[26].u.CCBSRequest.ComponentType = FacComponent_Result,
-	[26].u.CCBSRequest.Component.Result.RecallMode = 1,
-	[26].u.CCBSRequest.Component.Result.CCBSReference = 102,
-
-	[27].Function = Fac_CCBSInterrogate,
-	[27].u.CCBSInterrogate.InvokeID = 34,
-	[27].u.CCBSInterrogate.ComponentType = FacComponent_Invoke,
-	[27].u.CCBSInterrogate.Component.Invoke.AParty.Type = 8,
-	[27].u.CCBSInterrogate.Component.Invoke.AParty.LengthOfNumber = 4,
-	[27].u.CCBSInterrogate.Component.Invoke.AParty.Number = "1803",
-	[27].u.CCBSInterrogate.Component.Invoke.CCBSReferencePresent = 1,
-	[27].u.CCBSInterrogate.Component.Invoke.CCBSReference = 76,
-
-	[28].Function = Fac_CCBSInterrogate,
-	[28].u.CCBSInterrogate.InvokeID = 35,
-	[28].u.CCBSInterrogate.ComponentType = FacComponent_Invoke,
-	[28].u.CCBSInterrogate.Component.Invoke.AParty.Type = 8,
-	[28].u.CCBSInterrogate.Component.Invoke.AParty.LengthOfNumber = 4,
-	[28].u.CCBSInterrogate.Component.Invoke.AParty.Number = "1803",
-
-	[29].Function = Fac_CCBSInterrogate,
-	[29].u.CCBSInterrogate.InvokeID = 36,
-	[29].u.CCBSInterrogate.ComponentType = FacComponent_Invoke,
-	[29].u.CCBSInterrogate.Component.Invoke.CCBSReferencePresent = 1,
-	[29].u.CCBSInterrogate.Component.Invoke.CCBSReference = 76,
-
-	[30].Function = Fac_CCBSInterrogate,
-	[30].u.CCBSInterrogate.InvokeID = 37,
-	[30].u.CCBSInterrogate.ComponentType = FacComponent_Invoke,
-
-	[31].Function = Fac_CCBSInterrogate,
-	[31].u.CCBSInterrogate.InvokeID = 38,
-	[31].u.CCBSInterrogate.ComponentType = FacComponent_Result,
-	[31].u.CCBSInterrogate.Component.Result.RecallMode = 1,
-
-	[32].Function = Fac_CCBSInterrogate,
-	[32].u.CCBSInterrogate.InvokeID = 39,
-	[32].u.CCBSInterrogate.ComponentType = FacComponent_Result,
-	[32].u.CCBSInterrogate.Component.Result.RecallMode = 1,
-	[32].u.CCBSInterrogate.Component.Result.NumRecords = 1,
-	[32].u.CCBSInterrogate.Component.Result.CallDetails[0].CCBSReference = 12,
-	[32].u.CCBSInterrogate.Component.Result.CallDetails[0].Q931ie.Bc.Length = 2,
-	[32].u.CCBSInterrogate.Component.Result.CallDetails[0].Q931ie.Bc.Contents = "JK",
-	[32].u.CCBSInterrogate.Component.Result.CallDetails[0].AddressOfB.Party.Type = 8,
-	[32].u.CCBSInterrogate.Component.Result.CallDetails[0].AddressOfB.Party.LengthOfNumber = 4,
-	[32].u.CCBSInterrogate.Component.Result.CallDetails[0].AddressOfB.Party.Number = "1803",
-	[32].u.CCBSInterrogate.Component.Result.CallDetails[0].SubaddressOfA.Type = 1,
-	[32].u.CCBSInterrogate.Component.Result.CallDetails[0].SubaddressOfA.Length = 4,
-	[32].u.CCBSInterrogate.Component.Result.CallDetails[0].SubaddressOfA.u.Nsap = "6492",
-
-	[33].Function = Fac_CCBSInterrogate,
-	[33].u.CCBSInterrogate.InvokeID = 40,
-	[33].u.CCBSInterrogate.ComponentType = FacComponent_Result,
-	[33].u.CCBSInterrogate.Component.Result.RecallMode = 1,
-	[33].u.CCBSInterrogate.Component.Result.NumRecords = 2,
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[0].CCBSReference = 12,
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[0].Q931ie.Bc.Length = 2,
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[0].Q931ie.Bc.Contents = "JK",
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[0].AddressOfB.Party.Type = 8,
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[0].AddressOfB.Party.LengthOfNumber = 4,
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[0].AddressOfB.Party.Number = "1803",
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[1].CCBSReference = 102,
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[1].Q931ie.Bc.Length = 2,
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[1].Q931ie.Bc.Contents = "LM",
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[1].AddressOfB.Party.Type = 8,
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[1].AddressOfB.Party.LengthOfNumber = 4,
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[1].AddressOfB.Party.Number = "6229",
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[1].AddressOfB.Subaddress.Type = 1,
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[1].AddressOfB.Subaddress.Length = 4,
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[1].AddressOfB.Subaddress.u.Nsap = "8592",
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[1].SubaddressOfA.Type = 1,
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[1].SubaddressOfA.Length = 4,
-	[33].u.CCBSInterrogate.Component.Result.CallDetails[1].SubaddressOfA.u.Nsap = "6492",
-
-	[34].Function = Fac_CCNRRequest,
-	[34].u.CCNRRequest.InvokeID = 512,
-	[34].u.CCNRRequest.ComponentType = FacComponent_Invoke,
-	[34].u.CCNRRequest.Component.Invoke.CallLinkageID = 57,
-
-	[35].Function = Fac_CCNRRequest,
-	[35].u.CCNRRequest.InvokeID = 150,
-	[35].u.CCNRRequest.ComponentType = FacComponent_Result,
-	[35].u.CCNRRequest.Component.Result.RecallMode = 1,
-	[35].u.CCNRRequest.Component.Result.CCBSReference = 102,
-
-	[36].Function = Fac_CCNRInterrogate,
-	[36].u.CCNRInterrogate.InvokeID = -129,
-	[36].u.CCNRInterrogate.ComponentType = FacComponent_Invoke,
-
-	[37].Function = Fac_CCNRInterrogate,
-	[37].u.CCNRInterrogate.InvokeID = -3,
-	[37].u.CCNRInterrogate.ComponentType = FacComponent_Result,
-	[37].u.CCNRInterrogate.Component.Result.RecallMode = 1,
-
-	[38].Function = Fac_CCBS_T_Call,
-	[38].u.EctExecute.InvokeID = 41,
-
-	[39].Function = Fac_CCBS_T_Suspend,
-	[39].u.EctExecute.InvokeID = 42,
-
-	[40].Function = Fac_CCBS_T_Resume,
-	[40].u.EctExecute.InvokeID = 43,
-
-	[41].Function = Fac_CCBS_T_RemoteUserFree,
-	[41].u.EctExecute.InvokeID = 44,
-
-	[42].Function = Fac_CCBS_T_Available,
-	[42].u.EctExecute.InvokeID = 45,
-
-	[43].Function = Fac_CCBS_T_Request,
-	[43].u.CCBS_T_Request.InvokeID = 46,
-	[43].u.CCBS_T_Request.ComponentType = FacComponent_Invoke,
-	[43].u.CCBS_T_Request.Component.Invoke.Destination.Party.Type = 8,
-	[43].u.CCBS_T_Request.Component.Invoke.Destination.Party.LengthOfNumber = 4,
-	[43].u.CCBS_T_Request.Component.Invoke.Destination.Party.Number = "6229",
-	[43].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Length = 2,
-	[43].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Contents = "LM",
-	[43].u.CCBS_T_Request.Component.Invoke.RetentionSupported = 1,
-	[43].u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicatorPresent = 1,
-	[43].u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicator = 1,
-	[43].u.CCBS_T_Request.Component.Invoke.Originating.Party.Type = 8,
-	[43].u.CCBS_T_Request.Component.Invoke.Originating.Party.LengthOfNumber = 4,
-	[43].u.CCBS_T_Request.Component.Invoke.Originating.Party.Number = "9864",
-
-	[44].Function = Fac_CCBS_T_Request,
-	[44].u.CCBS_T_Request.InvokeID = 47,
-	[44].u.CCBS_T_Request.ComponentType = FacComponent_Invoke,
-	[44].u.CCBS_T_Request.Component.Invoke.Destination.Party.Type = 8,
-	[44].u.CCBS_T_Request.Component.Invoke.Destination.Party.LengthOfNumber = 4,
-	[44].u.CCBS_T_Request.Component.Invoke.Destination.Party.Number = "6229",
-	[44].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Length = 2,
-	[44].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Contents = "LM",
-	[44].u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicatorPresent = 1,
-	[44].u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicator = 1,
-	[44].u.CCBS_T_Request.Component.Invoke.Originating.Party.Type = 8,
-	[44].u.CCBS_T_Request.Component.Invoke.Originating.Party.LengthOfNumber = 4,
-	[44].u.CCBS_T_Request.Component.Invoke.Originating.Party.Number = "9864",
-
-	[45].Function = Fac_CCBS_T_Request,
-	[45].u.CCBS_T_Request.InvokeID = 48,
-	[45].u.CCBS_T_Request.ComponentType = FacComponent_Invoke,
-	[45].u.CCBS_T_Request.Component.Invoke.Destination.Party.Type = 8,
-	[45].u.CCBS_T_Request.Component.Invoke.Destination.Party.LengthOfNumber = 4,
-	[45].u.CCBS_T_Request.Component.Invoke.Destination.Party.Number = "6229",
-	[45].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Length = 2,
-	[45].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Contents = "LM",
-	[45].u.CCBS_T_Request.Component.Invoke.Originating.Party.Type = 8,
-	[45].u.CCBS_T_Request.Component.Invoke.Originating.Party.LengthOfNumber = 4,
-	[45].u.CCBS_T_Request.Component.Invoke.Originating.Party.Number = "9864",
-
-	[46].Function = Fac_CCBS_T_Request,
-	[46].u.CCBS_T_Request.InvokeID = 49,
-	[46].u.CCBS_T_Request.ComponentType = FacComponent_Invoke,
-	[46].u.CCBS_T_Request.Component.Invoke.Destination.Party.Type = 8,
-	[46].u.CCBS_T_Request.Component.Invoke.Destination.Party.LengthOfNumber = 4,
-	[46].u.CCBS_T_Request.Component.Invoke.Destination.Party.Number = "6229",
-	[46].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Length = 2,
-	[46].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Contents = "LM",
-	[46].u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicatorPresent = 1,
-	[46].u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicator = 1,
-
-	[47].Function = Fac_CCBS_T_Request,
-	[47].u.CCBS_T_Request.InvokeID = 50,
-	[47].u.CCBS_T_Request.ComponentType = FacComponent_Invoke,
-	[47].u.CCBS_T_Request.Component.Invoke.Destination.Party.Type = 8,
-	[47].u.CCBS_T_Request.Component.Invoke.Destination.Party.LengthOfNumber = 4,
-	[47].u.CCBS_T_Request.Component.Invoke.Destination.Party.Number = "6229",
-	[47].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Length = 2,
-	[47].u.CCBS_T_Request.Component.Invoke.Q931ie.Bc.Contents = "LM",
-
-	[48].Function = Fac_CCBS_T_Request,
-	[48].u.CCBS_T_Request.InvokeID = 51,
-	[48].u.CCBS_T_Request.ComponentType = FacComponent_Result,
-	[48].u.CCBS_T_Request.Component.Result.RetentionSupported = 1,
-
-	[49].Function = Fac_CCNR_T_Request,
-	[49].u.CCNR_T_Request.InvokeID = 52,
-	[49].u.CCNR_T_Request.ComponentType = FacComponent_Invoke,
-	[49].u.CCNR_T_Request.Component.Invoke.Destination.Party.Type = 8,
-	[49].u.CCNR_T_Request.Component.Invoke.Destination.Party.LengthOfNumber = 4,
-	[49].u.CCNR_T_Request.Component.Invoke.Destination.Party.Number = "6229",
-	[49].u.CCNR_T_Request.Component.Invoke.Q931ie.Bc.Length = 2,
-	[49].u.CCNR_T_Request.Component.Invoke.Q931ie.Bc.Contents = "LM",
-
-	[50].Function = Fac_CCNR_T_Request,
-	[50].u.CCNR_T_Request.InvokeID = 53,
-	[50].u.CCNR_T_Request.ComponentType = FacComponent_Result,
-	[50].u.CCNR_T_Request.Component.Result.RetentionSupported = 1,
-
-	[51].Function = Fac_EctExecute,
-	[51].u.EctExecute.InvokeID = 54,
-
-	[52].Function = Fac_ExplicitEctExecute,
-	[52].u.ExplicitEctExecute.InvokeID = 55,
-	[52].u.ExplicitEctExecute.LinkID = 23,
-
-	[53].Function = Fac_RequestSubaddress,
-	[53].u.RequestSubaddress.InvokeID = 56,
-
-	[54].Function = Fac_SubaddressTransfer,
-	[54].u.SubaddressTransfer.InvokeID = 57,
-	[54].u.SubaddressTransfer.Subaddress.Type = 1,
-	[54].u.SubaddressTransfer.Subaddress.Length = 4,
-	[54].u.SubaddressTransfer.Subaddress.u.Nsap = "6492",
-
-	[55].Function = Fac_EctLinkIdRequest,
-	[55].u.EctLinkIdRequest.InvokeID = 58,
-	[55].u.EctLinkIdRequest.ComponentType = FacComponent_Invoke,
-
-	[56].Function = Fac_EctLinkIdRequest,
-	[56].u.EctLinkIdRequest.InvokeID = 59,
-	[56].u.EctLinkIdRequest.ComponentType = FacComponent_Result,
-	[56].u.EctLinkIdRequest.Component.Result.LinkID = 76,
-
-	[57].Function = Fac_EctInform,
-	[57].u.EctInform.InvokeID = 60,
-	[57].u.EctInform.Status = 1,
-	[57].u.EctInform.RedirectionPresent = 1,
-	[57].u.EctInform.Redirection.Type = 0,
-	[57].u.EctInform.Redirection.Unscreened.Type = 8,
-	[57].u.EctInform.Redirection.Unscreened.LengthOfNumber = 4,
-	[57].u.EctInform.Redirection.Unscreened.Number = "6229",
-
-	[58].Function = Fac_EctInform,
-	[58].u.EctInform.InvokeID = 61,
-	[58].u.EctInform.Status = 1,
-	[58].u.EctInform.RedirectionPresent = 1,
-	[58].u.EctInform.Redirection.Type = 1,
-
-	[59].Function = Fac_EctInform,
-	[59].u.EctInform.InvokeID = 62,
-	[59].u.EctInform.Status = 1,
-	[59].u.EctInform.RedirectionPresent = 1,
-	[59].u.EctInform.Redirection.Type = 2,
-
-	[60].Function = Fac_EctInform,
-	[60].u.EctInform.InvokeID = 63,
-	[60].u.EctInform.Status = 1,
-	[60].u.EctInform.RedirectionPresent = 1,
-	[60].u.EctInform.Redirection.Type = 3,
-	[60].u.EctInform.Redirection.Unscreened.Type = 8,
-	[60].u.EctInform.Redirection.Unscreened.LengthOfNumber = 4,
-	[60].u.EctInform.Redirection.Unscreened.Number = "3340",
-
-	[61].Function = Fac_EctInform,
-	[61].u.EctInform.InvokeID = 64,
-	[61].u.EctInform.Status = 1,
-	[61].u.EctInform.RedirectionPresent = 0,
-
-	[62].Function = Fac_EctLoopTest,
-	[62].u.EctLoopTest.InvokeID = 65,
-	[62].u.EctLoopTest.ComponentType = FacComponent_Invoke,
-	[62].u.EctLoopTest.Component.Invoke.CallTransferID = 7,
-
-	[63].Function = Fac_EctLoopTest,
-	[63].u.EctLoopTest.InvokeID = 66,
-	[63].u.EctLoopTest.ComponentType = FacComponent_Result,
-	[63].u.EctLoopTest.Component.Result.LoopResult = 2,
-
-	[64].Function = Fac_ActivationDiversion,
-	[64].u.ActivationDiversion.InvokeID = 67,
-	[64].u.ActivationDiversion.ComponentType = FacComponent_Invoke,
-	[64].u.ActivationDiversion.Component.Invoke.Procedure = 2,
-	[64].u.ActivationDiversion.Component.Invoke.BasicService = 3,
-	[64].u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Type = 4,
-	[64].u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.LengthOfNumber = 4,
-	[64].u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Number = "1803",
-	[64].u.ActivationDiversion.Component.Invoke.ServedUser.Type = 4,
-	[64].u.ActivationDiversion.Component.Invoke.ServedUser.LengthOfNumber = 4,
-	[64].u.ActivationDiversion.Component.Invoke.ServedUser.Number = "5398",
-
-	[65].Function = Fac_ActivationDiversion,
-	[65].u.ActivationDiversion.InvokeID = 68,
-	[65].u.ActivationDiversion.ComponentType = FacComponent_Invoke,
-	[65].u.ActivationDiversion.Component.Invoke.Procedure = 1,
-	[65].u.ActivationDiversion.Component.Invoke.BasicService = 5,
-	[65].u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Type = 4,
-	[65].u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.LengthOfNumber = 4,
-	[65].u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Number = "1803",
-
-	[66].Function = Fac_ActivationDiversion,
-	[66].u.ActivationDiversion.InvokeID = 69,
-	[66].u.ActivationDiversion.ComponentType = FacComponent_Result,
-
-	[67].Function = Fac_DeactivationDiversion,
-	[67].u.DeactivationDiversion.InvokeID = 70,
-	[67].u.DeactivationDiversion.ComponentType = FacComponent_Invoke,
-	[67].u.DeactivationDiversion.Component.Invoke.Procedure = 1,
-	[67].u.DeactivationDiversion.Component.Invoke.BasicService = 5,
-
-	[68].Function = Fac_DeactivationDiversion,
-	[68].u.DeactivationDiversion.InvokeID = 71,
-	[68].u.DeactivationDiversion.ComponentType = FacComponent_Result,
-
-	[69].Function = Fac_ActivationStatusNotificationDiv,
-	[69].u.ActivationStatusNotificationDiv.InvokeID = 72,
-	[69].u.ActivationStatusNotificationDiv.Procedure = 1,
-	[69].u.ActivationStatusNotificationDiv.BasicService = 5,
-	[69].u.ActivationStatusNotificationDiv.ForwardedTo.Party.Type = 4,
-	[69].u.ActivationStatusNotificationDiv.ForwardedTo.Party.LengthOfNumber = 4,
-	[69].u.ActivationStatusNotificationDiv.ForwardedTo.Party.Number = "1803",
-
-	[70].Function = Fac_DeactivationStatusNotificationDiv,
-	[70].u.DeactivationStatusNotificationDiv.InvokeID = 73,
-	[70].u.DeactivationStatusNotificationDiv.Procedure = 1,
-	[70].u.DeactivationStatusNotificationDiv.BasicService = 5,
-
-	[71].Function = Fac_InterrogationDiversion,
-	[71].u.InterrogationDiversion.InvokeID = 74,
-	[71].u.InterrogationDiversion.ComponentType = FacComponent_Invoke,
-	[71].u.InterrogationDiversion.Component.Invoke.Procedure = 1,
-	[71].u.InterrogationDiversion.Component.Invoke.BasicService = 5,
-
-	[72].Function = Fac_InterrogationDiversion,
-	[72].u.InterrogationDiversion.InvokeID = 75,
-	[72].u.InterrogationDiversion.ComponentType = FacComponent_Invoke,
-	[72].u.InterrogationDiversion.Component.Invoke.Procedure = 1,
-
-	[73].Function = Fac_InterrogationDiversion,
-	[73].u.InterrogationDiversion.InvokeID = 76,
-	[73].u.InterrogationDiversion.ComponentType = FacComponent_Result,
-	[73].u.InterrogationDiversion.Component.Result.NumRecords = 2,
-	[73].u.InterrogationDiversion.Component.Result.List[0].Procedure = 2,
-	[73].u.InterrogationDiversion.Component.Result.List[0].BasicService = 5,
-	[73].u.InterrogationDiversion.Component.Result.List[0].ForwardedTo.Party.Type = 4,
-	[73].u.InterrogationDiversion.Component.Result.List[0].ForwardedTo.Party.LengthOfNumber = 4,
-	[73].u.InterrogationDiversion.Component.Result.List[0].ForwardedTo.Party.Number = "1803",
-	[73].u.InterrogationDiversion.Component.Result.List[1].Procedure = 1,
-	[73].u.InterrogationDiversion.Component.Result.List[1].BasicService = 3,
-	[73].u.InterrogationDiversion.Component.Result.List[1].ForwardedTo.Party.Type = 4,
-	[73].u.InterrogationDiversion.Component.Result.List[1].ForwardedTo.Party.LengthOfNumber = 4,
-	[73].u.InterrogationDiversion.Component.Result.List[1].ForwardedTo.Party.Number = "1903",
-	[73].u.InterrogationDiversion.Component.Result.List[1].ServedUser.Type = 4,
-	[73].u.InterrogationDiversion.Component.Result.List[1].ServedUser.LengthOfNumber = 4,
-	[73].u.InterrogationDiversion.Component.Result.List[1].ServedUser.Number = "5398",
-
-	[74].Function = Fac_DiversionInformation,
-	[74].u.DiversionInformation.InvokeID = 77,
-	[74].u.DiversionInformation.DiversionReason = 3,
-	[74].u.DiversionInformation.BasicService = 5,
-	[74].u.DiversionInformation.ServedUserSubaddress.Type = 1,
-	[74].u.DiversionInformation.ServedUserSubaddress.Length = 4,
-	[74].u.DiversionInformation.ServedUserSubaddress.u.Nsap = "6492",
-	[74].u.DiversionInformation.CallingAddressPresent = 1,
-	[74].u.DiversionInformation.CallingAddress.Type = 0,
-	[74].u.DiversionInformation.CallingAddress.Address.ScreeningIndicator = 3,
-	[74].u.DiversionInformation.CallingAddress.Address.Party.Type = 4,
-	[74].u.DiversionInformation.CallingAddress.Address.Party.LengthOfNumber = 4,
-	[74].u.DiversionInformation.CallingAddress.Address.Party.Number = "1803",
-	[74].u.DiversionInformation.OriginalCalledPresent = 1,
-	[74].u.DiversionInformation.OriginalCalled.Type = 1,
-	[74].u.DiversionInformation.LastDivertingPresent = 1,
-	[74].u.DiversionInformation.LastDiverting.Type = 2,
-	[74].u.DiversionInformation.LastDivertingReasonPresent = 1,
-	[74].u.DiversionInformation.LastDivertingReason = 3,
-	[74].u.DiversionInformation.UserInfo.Length = 5,
-	[74].u.DiversionInformation.UserInfo.Contents = "79828",
-
-	[75].Function = Fac_DiversionInformation,
-	[75].u.DiversionInformation.InvokeID = 78,
-	[75].u.DiversionInformation.DiversionReason = 3,
-	[75].u.DiversionInformation.BasicService = 5,
-	[75].u.DiversionInformation.CallingAddressPresent = 1,
-	[75].u.DiversionInformation.CallingAddress.Type = 1,
-	[75].u.DiversionInformation.OriginalCalledPresent = 1,
-	[75].u.DiversionInformation.OriginalCalled.Type = 2,
-	[75].u.DiversionInformation.LastDivertingPresent = 1,
-	[75].u.DiversionInformation.LastDiverting.Type = 1,
-
-	[76].Function = Fac_DiversionInformation,
-	[76].u.DiversionInformation.InvokeID = 79,
-	[76].u.DiversionInformation.DiversionReason = 2,
-	[76].u.DiversionInformation.BasicService = 3,
-	[76].u.DiversionInformation.CallingAddressPresent = 1,
-	[76].u.DiversionInformation.CallingAddress.Type = 2,
-
-	[77].Function = Fac_DiversionInformation,
-	[77].u.DiversionInformation.InvokeID = 80,
-	[77].u.DiversionInformation.DiversionReason = 3,
-	[77].u.DiversionInformation.BasicService = 5,
-	[77].u.DiversionInformation.CallingAddressPresent = 1,
-	[77].u.DiversionInformation.CallingAddress.Type = 3,
-	[77].u.DiversionInformation.CallingAddress.Address.ScreeningIndicator = 2,
-	[77].u.DiversionInformation.CallingAddress.Address.Party.Type = 4,
-	[77].u.DiversionInformation.CallingAddress.Address.Party.LengthOfNumber = 4,
-	[77].u.DiversionInformation.CallingAddress.Address.Party.Number = "1803",
-
-	[78].Function = Fac_DiversionInformation,
-	[78].u.DiversionInformation.InvokeID = 81,
-	[78].u.DiversionInformation.DiversionReason = 2,
-	[78].u.DiversionInformation.BasicService = 4,
-	[78].u.DiversionInformation.UserInfo.Length = 5,
-	[78].u.DiversionInformation.UserInfo.Contents = "79828",
-
-	[79].Function = Fac_DiversionInformation,
-	[79].u.DiversionInformation.InvokeID = 82,
-	[79].u.DiversionInformation.DiversionReason = 2,
-	[79].u.DiversionInformation.BasicService = 4,
-
-	[80].Function = Fac_CallDeflection,
-	[80].u.CallDeflection.InvokeID = 83,
-	[80].u.CallDeflection.ComponentType = FacComponent_Invoke,
-	[80].u.CallDeflection.Component.Invoke.Deflection.Party.Type = 4,
-	[80].u.CallDeflection.Component.Invoke.Deflection.Party.LengthOfNumber = 4,
-	[80].u.CallDeflection.Component.Invoke.Deflection.Party.Number = "1803",
-	[80].u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUserPresent = 1,
-	[80].u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUser = 1,
-
-	[81].Function = Fac_CallDeflection,
-	[81].u.CallDeflection.InvokeID = 84,
-	[81].u.CallDeflection.ComponentType = FacComponent_Invoke,
-	[81].u.CallDeflection.Component.Invoke.Deflection.Party.Type = 4,
-	[81].u.CallDeflection.Component.Invoke.Deflection.Party.LengthOfNumber = 4,
-	[81].u.CallDeflection.Component.Invoke.Deflection.Party.Number = "1803",
-	[81].u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUserPresent = 1,
-	[81].u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUser = 0,
-
-	[82].Function = Fac_CallDeflection,
-	[82].u.CallDeflection.InvokeID = 85,
-	[82].u.CallDeflection.ComponentType = FacComponent_Invoke,
-	[82].u.CallDeflection.Component.Invoke.Deflection.Party.Type = 4,
-	[82].u.CallDeflection.Component.Invoke.Deflection.Party.LengthOfNumber = 4,
-	[82].u.CallDeflection.Component.Invoke.Deflection.Party.Number = "1803",
-
-	[83].Function = Fac_CallDeflection,
-	[83].u.CallDeflection.InvokeID = 86,
-	[83].u.CallDeflection.ComponentType = FacComponent_Result,
-
-	[84].Function = Fac_CallRerouteing,
-	[84].u.CallRerouteing.InvokeID = 87,
-	[84].u.CallRerouteing.ComponentType = FacComponent_Invoke,
-	[84].u.CallRerouteing.Component.Invoke.ReroutingReason = 3,
-	[84].u.CallRerouteing.Component.Invoke.ReroutingCounter = 2,
-	[84].u.CallRerouteing.Component.Invoke.CalledAddress.Party.Type = 4,
-	[84].u.CallRerouteing.Component.Invoke.CalledAddress.Party.LengthOfNumber = 4,
-	[84].u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number = "1803",
-	[84].u.CallRerouteing.Component.Invoke.Q931ie.Bc.Length = 2,
-	[84].u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents = "RT",
-	[84].u.CallRerouteing.Component.Invoke.Q931ie.Hlc.Length = 3,
-	[84].u.CallRerouteing.Component.Invoke.Q931ie.Hlc.Contents = "RTG",
-	[84].u.CallRerouteing.Component.Invoke.Q931ie.Llc.Length = 2,
-	[84].u.CallRerouteing.Component.Invoke.Q931ie.Llc.Contents = "MY",
-	[84].u.CallRerouteing.Component.Invoke.Q931ie.UserInfo.Length = 5,
-	[84].u.CallRerouteing.Component.Invoke.Q931ie.UserInfo.Contents = "YEHAW",
-	[84].u.CallRerouteing.Component.Invoke.LastRerouting.Type = 1,
-	[84].u.CallRerouteing.Component.Invoke.SubscriptionOption = 2,
-	[84].u.CallRerouteing.Component.Invoke.CallingPartySubaddress.Type = 1,
-	[84].u.CallRerouteing.Component.Invoke.CallingPartySubaddress.Length = 4,
-	[84].u.CallRerouteing.Component.Invoke.CallingPartySubaddress.u.Nsap = "6492",
-
-	[85].Function = Fac_CallRerouteing,
-	[85].u.CallRerouteing.InvokeID = 88,
-	[85].u.CallRerouteing.ComponentType = FacComponent_Invoke,
-	[85].u.CallRerouteing.Component.Invoke.ReroutingReason = 3,
-	[85].u.CallRerouteing.Component.Invoke.ReroutingCounter = 2,
-	[85].u.CallRerouteing.Component.Invoke.CalledAddress.Party.Type = 4,
-	[85].u.CallRerouteing.Component.Invoke.CalledAddress.Party.LengthOfNumber = 4,
-	[85].u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number = "1803",
-	[85].u.CallRerouteing.Component.Invoke.Q931ie.Bc.Length = 2,
-	[85].u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents = "RT",
-	[85].u.CallRerouteing.Component.Invoke.LastRerouting.Type = 1,
-	[85].u.CallRerouteing.Component.Invoke.SubscriptionOption = 2,
-
-	[86].Function = Fac_CallRerouteing,
-	[86].u.CallRerouteing.InvokeID = 89,
-	[86].u.CallRerouteing.ComponentType = FacComponent_Invoke,
-	[86].u.CallRerouteing.Component.Invoke.ReroutingReason = 3,
-	[86].u.CallRerouteing.Component.Invoke.ReroutingCounter = 2,
-	[86].u.CallRerouteing.Component.Invoke.CalledAddress.Party.Type = 4,
-	[86].u.CallRerouteing.Component.Invoke.CalledAddress.Party.LengthOfNumber = 4,
-	[86].u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number = "1803",
-	[86].u.CallRerouteing.Component.Invoke.Q931ie.Bc.Length = 2,
-	[86].u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents = "RT",
-	[86].u.CallRerouteing.Component.Invoke.LastRerouting.Type = 2,
-
-	[87].Function = Fac_CallRerouteing,
-	[87].u.CallRerouteing.InvokeID = 90,
-	[87].u.CallRerouteing.ComponentType = FacComponent_Result,
-
-	[88].Function = Fac_InterrogateServedUserNumbers,
-	[88].u.InterrogateServedUserNumbers.InvokeID = 91,
-	[88].u.InterrogateServedUserNumbers.ComponentType = FacComponent_Invoke,
-
-	[89].Function = Fac_InterrogateServedUserNumbers,
-	[89].u.InterrogateServedUserNumbers.InvokeID = 92,
-	[89].u.InterrogateServedUserNumbers.ComponentType = FacComponent_Result,
-	[89].u.InterrogateServedUserNumbers.Component.Result.NumRecords = 2,
-	[89].u.InterrogateServedUserNumbers.Component.Result.List[0].Type = 4,
-	[89].u.InterrogateServedUserNumbers.Component.Result.List[0].LengthOfNumber = 4,
-	[89].u.InterrogateServedUserNumbers.Component.Result.List[0].Number = "1803",
-	[89].u.InterrogateServedUserNumbers.Component.Result.List[1].Type = 4,
-	[89].u.InterrogateServedUserNumbers.Component.Result.List[1].LengthOfNumber = 4,
-	[89].u.InterrogateServedUserNumbers.Component.Result.List[1].Number = "5786",
-
-	[90].Function = Fac_DivertingLegInformation1,
-	[90].u.DivertingLegInformation1.InvokeID = 93,
-	[90].u.DivertingLegInformation1.DiversionReason = 4,
-	[90].u.DivertingLegInformation1.SubscriptionOption = 1,
-	[90].u.DivertingLegInformation1.DivertedToPresent = 1,
-	[90].u.DivertingLegInformation1.DivertedTo.Type = 2,
-
-	[91].Function = Fac_DivertingLegInformation1,
-	[91].u.DivertingLegInformation1.InvokeID = 94,
-	[91].u.DivertingLegInformation1.DiversionReason = 4,
-	[91].u.DivertingLegInformation1.SubscriptionOption = 1,
-
-	[92].Function = Fac_DivertingLegInformation2,
-	[92].u.DivertingLegInformation2.InvokeID = 95,
-	[92].u.DivertingLegInformation2.DiversionCounter = 3,
-	[92].u.DivertingLegInformation2.DiversionReason = 2,
-	[92].u.DivertingLegInformation2.DivertingPresent = 1,
-	[92].u.DivertingLegInformation2.Diverting.Type = 2,
-	[92].u.DivertingLegInformation2.OriginalCalledPresent = 1,
-	[92].u.DivertingLegInformation2.OriginalCalled.Type = 1,
-
-	[93].Function = Fac_DivertingLegInformation2,
-	[93].u.DivertingLegInformation2.InvokeID = 96,
-	[93].u.DivertingLegInformation2.DiversionCounter = 3,
-	[93].u.DivertingLegInformation2.DiversionReason = 2,
-	[93].u.DivertingLegInformation2.OriginalCalledPresent = 1,
-	[93].u.DivertingLegInformation2.OriginalCalled.Type = 1,
-
-	[94].Function = Fac_DivertingLegInformation2,
-	[94].u.DivertingLegInformation2.InvokeID = 97,
-	[94].u.DivertingLegInformation2.DiversionCounter = 1,
-	[94].u.DivertingLegInformation2.DiversionReason = 2,
-
-	[95].Function = Fac_DivertingLegInformation3,
-	[95].u.DivertingLegInformation3.InvokeID = 98,
-	[95].u.DivertingLegInformation3.PresentationAllowedIndicator = 1,
-/* *INDENT-ON* */
-};
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) && defined(CCBS_TEST_MESSAGES) */
-
-static char *handle_cli_misdn_send_facility(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	const char *channame;
-	const char *nr;
-	struct chan_list *tmp;
-	int port;
-	const char *served_nr;
-	struct misdn_bchannel dummy, *bc=&dummy;
-	unsigned max_len;
-
- 	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn send facility";
-		e->usage = "Usage: misdn send facility <type> <channel|port> \"<args>\" \n"
-		"\t type is one of:\n"
-		"\t - calldeflect\n"
-#if defined(AST_MISDN_ENHANCEMENTS)
-		"\t - callrerouting\n"
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-		"\t - CFActivate\n"
-		"\t - CFDeactivate\n";
-
-		return NULL;
-	case CLI_GENERATE:
-		return complete_ch(a);
-	}
-
-	if (a->argc < 5) {
-		return CLI_SHOWUSAGE;
-	}
-
-	if (strstr(a->argv[3], "calldeflect")) {
-		if (a->argc < 6) {
-			ast_verbose("calldeflect requires 1 arg: ToNumber\n\n");
-			return 0;
-		}
-		channame = a->argv[4];
-		nr = a->argv[5];
-
-		ast_verbose("Sending Calldeflection (%s) to %s\n", nr, channame);
-		tmp = get_chan_by_ast_name(channame);
-		if (!tmp) {
-			ast_verbose("Sending CD with nr %s to %s failed: Channel does not exist.\n", nr, channame);
-			return 0;
-		}
-		ao2_lock(tmp);
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-		max_len = sizeof(tmp->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.Number) - 1;
-		if (max_len < strlen(nr)) {
-			ast_verbose("Sending CD with nr %s to %s failed: Number too long (up to %u digits are allowed).\n",
-				nr, channame, max_len);
-			ao2_unlock(tmp);
-			chan_list_unref(tmp, "Number too long");
-			return 0;
-		}
-		tmp->bc->fac_out.Function = Fac_CallDeflection;
-		tmp->bc->fac_out.u.CallDeflection.InvokeID = ++misdn_invoke_id;
-		tmp->bc->fac_out.u.CallDeflection.ComponentType = FacComponent_Invoke;
-		tmp->bc->fac_out.u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUserPresent = 1;
-		tmp->bc->fac_out.u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUser = 0;
-		tmp->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.Type = 0;/* unknown */
-		tmp->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.LengthOfNumber = strlen(nr);
-		strcpy((char *) tmp->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.Number, nr);
-		tmp->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Subaddress.Length = 0;
-
-#else	/* !defined(AST_MISDN_ENHANCEMENTS) */
-
-		max_len = sizeof(tmp->bc->fac_out.u.CDeflection.DeflectedToNumber) - 1;
-		if (max_len < strlen(nr)) {
-			ast_verbose("Sending CD with nr %s to %s failed: Number too long (up to %u digits are allowed).\n",
-				nr, channame, max_len);
-			ao2_unlock(tmp);
-			chan_list_unref(tmp, "Number too long");
-			return 0;
-		}
-		tmp->bc->fac_out.Function = Fac_CD;
-		tmp->bc->fac_out.u.CDeflection.PresentationAllowed = 0;
-		//tmp->bc->fac_out.u.CDeflection.DeflectedToSubaddress[0] = 0;
-		strcpy((char *) tmp->bc->fac_out.u.CDeflection.DeflectedToNumber, nr);
-#endif	/* !defined(AST_MISDN_ENHANCEMENTS) */
-
-		/* Send message */
-		print_facility(&tmp->bc->fac_out, tmp->bc);
-		ao2_unlock(tmp);
-		misdn_lib_send_event(tmp->bc, EVENT_FACILITY);
-		chan_list_unref(tmp, "Send facility complete");
-#if defined(AST_MISDN_ENHANCEMENTS)
-	} else if (strstr(a->argv[3], "callrerouteing") || strstr(a->argv[3], "callrerouting")) {
-		if (a->argc < 6) {
-			ast_verbose("callrerouting requires 1 arg: ToNumber\n\n");
-			return 0;
-		}
-		channame = a->argv[4];
-		nr = a->argv[5];
-
-		ast_verbose("Sending Callrerouting (%s) to %s\n", nr, channame);
-		tmp = get_chan_by_ast_name(channame);
-		if (!tmp) {
-			ast_verbose("Sending Call Rerouting with nr %s to %s failed: Channel does not exist.\n", nr, channame);
-			return 0;
-		}
-		ao2_lock(tmp);
-
-		max_len = sizeof(tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number) - 1;
-		if (max_len < strlen(nr)) {
-			ast_verbose("Sending Call Rerouting with nr %s to %s failed: Number too long (up to %u digits are allowed).\n",
-				nr, channame, max_len);
-			ao2_unlock(tmp);
-			chan_list_unref(tmp, "Number too long");
-			return 0;
-		}
-		tmp->bc->fac_out.Function = Fac_CallRerouteing;
-		tmp->bc->fac_out.u.CallRerouteing.InvokeID = ++misdn_invoke_id;
-		tmp->bc->fac_out.u.CallRerouteing.ComponentType = FacComponent_Invoke;
-
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.ReroutingReason = 0;/* unknown */
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.ReroutingCounter = 1;
-
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.Type = 0;/* unknown */
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.LengthOfNumber = strlen(nr);
-		strcpy((char *) tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number, nr);
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Subaddress.Length = 0;
-
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.CallingPartySubaddress.Length = 0;
-
-		/* 0x90 0x90 0xa3 3.1 kHz audio, circuit mode, 64kbit/sec, level1/a-Law */
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Length = 3;
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents[0] = 0x90;
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents[1] = 0x90;
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents[2] = 0xa3;
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Hlc.Length = 0;
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Llc.Length = 0;
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.UserInfo.Length = 0;
-
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.LastRerouting.Type = 1;/* presentationRestricted */
-		tmp->bc->fac_out.u.CallRerouteing.Component.Invoke.SubscriptionOption = 0;/* no notification to caller */
-
-		/* Send message */
-		print_facility(&tmp->bc->fac_out, tmp->bc);
-		ao2_unlock(tmp);
-		misdn_lib_send_event(tmp->bc, EVENT_FACILITY);
-		chan_list_unref(tmp, "Send facility complete");
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	} else if (strstr(a->argv[3], "CFActivate")) {
-		if (a->argc < 7) {
-			ast_verbose("CFActivate requires 2 args: 1.FromNumber, 2.ToNumber\n\n");
-			return 0;
-		}
-		port = atoi(a->argv[4]);
-		served_nr = a->argv[5];
-		nr = a->argv[6];
-
-		misdn_make_dummy(bc, port, 0, misdn_lib_port_is_nt(port), 0);
-
-		ast_verbose("Sending CFActivate  Port:(%d) FromNr. (%s) to Nr. (%s)\n", port, served_nr, nr);
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-		bc->fac_out.Function = Fac_ActivationDiversion;
-		bc->fac_out.u.ActivationDiversion.InvokeID = ++misdn_invoke_id;
-		bc->fac_out.u.ActivationDiversion.ComponentType = FacComponent_Invoke;
-		bc->fac_out.u.ActivationDiversion.Component.Invoke.BasicService = 0;/* allServices */
-		bc->fac_out.u.ActivationDiversion.Component.Invoke.Procedure = 0;/* cfu (Call Forward Unconditional) */
-		ast_copy_string((char *) bc->fac_out.u.ActivationDiversion.Component.Invoke.ServedUser.Number,
-			served_nr, sizeof(bc->fac_out.u.ActivationDiversion.Component.Invoke.ServedUser.Number));
-		bc->fac_out.u.ActivationDiversion.Component.Invoke.ServedUser.LengthOfNumber =
-			strlen((char *) bc->fac_out.u.ActivationDiversion.Component.Invoke.ServedUser.Number);
-		bc->fac_out.u.ActivationDiversion.Component.Invoke.ServedUser.Type = 0;/* unknown */
-		ast_copy_string((char *) bc->fac_out.u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Number,
-			nr, sizeof(bc->fac_out.u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Number));
-		bc->fac_out.u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.LengthOfNumber =
-			strlen((char *) bc->fac_out.u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Number);
-		bc->fac_out.u.ActivationDiversion.Component.Invoke.ForwardedTo.Party.Type = 0;/* unknown */
-		bc->fac_out.u.ActivationDiversion.Component.Invoke.ForwardedTo.Subaddress.Length = 0;
-
-#else	/* !defined(AST_MISDN_ENHANCEMENTS) */
-
-		bc->fac_out.Function = Fac_CFActivate;
-		bc->fac_out.u.CFActivate.BasicService = 0; /* All Services */
-		bc->fac_out.u.CFActivate.Procedure = 0; /* Unconditional */
-		ast_copy_string((char *) bc->fac_out.u.CFActivate.ServedUserNumber, served_nr, sizeof(bc->fac_out.u.CFActivate.ServedUserNumber));
-		ast_copy_string((char *) bc->fac_out.u.CFActivate.ForwardedToNumber, nr, sizeof(bc->fac_out.u.CFActivate.ForwardedToNumber));
-#endif	/* !defined(AST_MISDN_ENHANCEMENTS) */
-
-		/* Send message */
-		print_facility(&bc->fac_out, bc);
-		misdn_lib_send_event(bc, EVENT_FACILITY);
-	} else if (strstr(a->argv[3], "CFDeactivate")) {
-		if (a->argc < 6) {
-			ast_verbose("CFDeactivate requires 1 arg: FromNumber\n\n");
-			return 0;
-		}
-		port = atoi(a->argv[4]);
-		served_nr = a->argv[5];
-
-		misdn_make_dummy(bc, port, 0, misdn_lib_port_is_nt(port), 0);
-		ast_verbose("Sending CFDeactivate  Port:(%d) FromNr. (%s)\n", port, served_nr);
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-		bc->fac_out.Function = Fac_DeactivationDiversion;
-		bc->fac_out.u.DeactivationDiversion.InvokeID = ++misdn_invoke_id;
-		bc->fac_out.u.DeactivationDiversion.ComponentType = FacComponent_Invoke;
-		bc->fac_out.u.DeactivationDiversion.Component.Invoke.BasicService = 0;/* allServices */
-		bc->fac_out.u.DeactivationDiversion.Component.Invoke.Procedure = 0;/* cfu (Call Forward Unconditional) */
-		ast_copy_string((char *) bc->fac_out.u.DeactivationDiversion.Component.Invoke.ServedUser.Number,
-			served_nr, sizeof(bc->fac_out.u.DeactivationDiversion.Component.Invoke.ServedUser.Number));
-		bc->fac_out.u.DeactivationDiversion.Component.Invoke.ServedUser.LengthOfNumber =
-			strlen((char *) bc->fac_out.u.DeactivationDiversion.Component.Invoke.ServedUser.Number);
-		bc->fac_out.u.DeactivationDiversion.Component.Invoke.ServedUser.Type = 0;/* unknown */
-
-#else	/* !defined(AST_MISDN_ENHANCEMENTS) */
-
-		bc->fac_out.Function = Fac_CFDeactivate;
-		bc->fac_out.u.CFDeactivate.BasicService = 0; /* All Services */
-		bc->fac_out.u.CFDeactivate.Procedure = 0; /* Unconditional */
-		ast_copy_string((char *) bc->fac_out.u.CFActivate.ServedUserNumber, served_nr, sizeof(bc->fac_out.u.CFActivate.ServedUserNumber));
-#endif	/* !defined(AST_MISDN_ENHANCEMENTS) */
-
-		/* Send message */
-		print_facility(&bc->fac_out, bc);
-		misdn_lib_send_event(bc, EVENT_FACILITY);
-#if defined(AST_MISDN_ENHANCEMENTS) && defined(CCBS_TEST_MESSAGES)
-	} else if (strstr(a->argv[3], "test")) {
-		int msg_number;
-
-		if (a->argc < 5) {
-			ast_verbose("test (<port> [<msg#>]) | (<channel-name> <msg#>)\n\n");
-			return 0;
-		}
-		port = atoi(a->argv[4]);
-
-		channame = a->argv[4];
-		tmp = get_chan_by_ast_name(channame);
-		if (tmp) {
-			/* We are going to send this FACILITY message out on an existing connection */
-			msg_number = atoi(a->argv[5]);
-			if (msg_number < ARRAY_LEN(Fac_Msgs)) {
-				ao2_lock(tmp);
-				tmp->bc->fac_out = Fac_Msgs[msg_number];
-
-				/* Send message */
-				print_facility(&tmp->bc->fac_out, tmp->bc);
-				ao2_unlock(tmp);
-				misdn_lib_send_event(tmp->bc, EVENT_FACILITY);
-			} else {
-				ast_verbose("test <channel-name> <msg#>\n\n");
-			}
-			chan_list_unref(tmp, "Facility test done");
-		} else if (a->argc < 6) {
-			for (msg_number = 0; msg_number < ARRAY_LEN(Fac_Msgs); ++msg_number) {
-				misdn_make_dummy(bc, port, 0, misdn_lib_port_is_nt(port), 0);
-				bc->fac_out = Fac_Msgs[msg_number];
-
-				/* Send message */
-				print_facility(&bc->fac_out, bc);
-				misdn_lib_send_event(bc, EVENT_FACILITY);
-				sleep(1);
-			}
-		} else {
-			msg_number = atoi(a->argv[5]);
-			if (msg_number < ARRAY_LEN(Fac_Msgs)) {
-				misdn_make_dummy(bc, port, 0, misdn_lib_port_is_nt(port), 0);
-				bc->fac_out = Fac_Msgs[msg_number];
-
-				/* Send message */
-				print_facility(&bc->fac_out, bc);
-				misdn_lib_send_event(bc, EVENT_FACILITY);
-			} else {
-				ast_verbose("test <port> [<msg#>]\n\n");
-			}
-		}
-	} else if (strstr(a->argv[3], "register")) {
-		if (a->argc < 5) {
-			ast_verbose("register <port>\n\n");
-			return 0;
-		}
-		port = atoi(a->argv[4]);
-
-		bc = misdn_lib_get_register_bc(port);
-		if (!bc) {
-			ast_verbose("Could not allocate REGISTER bc struct\n\n");
-			return 0;
-		}
-		bc->fac_out = Fac_Msgs[45];
-
-		/* Send message */
-		print_facility(&bc->fac_out, bc);
-		misdn_lib_send_event(bc, EVENT_REGISTER);
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) && defined(CCBS_TEST_MESSAGES) */
-	}
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_send_restart(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	int port;
-	int channel;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn send restart";
-		e->usage =
-			"Usage: misdn send restart [port [channel]]\n"
-			"       Send a restart for every bchannel on the given port.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc < 4 || a->argc > 5) {
-		return CLI_SHOWUSAGE;
-	}
-
-	port = atoi(a->argv[3]);
-
-	if (a->argc == 5) {
-		channel = atoi(a->argv[4]);
-		misdn_lib_send_restart(port, channel);
-	} else {
- 		misdn_lib_send_restart(port, -1);
-	}
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_send_digit(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	const char *channame;
-	const char *msg;
-	struct chan_list *tmp;
-	int i, msglen;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn send digit";
-		e->usage =
-			"Usage: misdn send digit <channel> \"<msg>\" \n"
-			"       Send <digit> to <channel> as DTMF Tone\n"
-			"       when channel is a mISDN channel\n";
-		return NULL;
-	case CLI_GENERATE:
-		return complete_ch(a);
-	}
-
-	if (a->argc != 5) {
-		return CLI_SHOWUSAGE;
-	}
-
-	channame = a->argv[3];
-	msg = a->argv[4];
-	msglen = strlen(msg);
-
-	ast_cli(a->fd, "Sending %s to %s\n", msg, channame);
-
-	tmp = get_chan_by_ast_name(channame);
-	if (!tmp) {
-		ast_cli(a->fd, "Sending %s to %s failed Channel does not exist\n", msg, channame);
-		return CLI_SUCCESS;
-	}
-#if 1
-	for (i = 0; i < msglen; i++) {
-		if (!tmp->ast) {
-			break;
-		}
-		ast_cli(a->fd, "Sending: %c\n", msg[i]);
-		send_digit_to_chan(tmp, msg[i]);
-		/* res = ast_safe_sleep(tmp->ast, 250); */
-		usleep(250000);
-		/* res = ast_waitfor(tmp->ast,100); */
-	}
-#else
-	if (tmp->ast) {
-		ast_dtmf_stream(tmp->ast, NULL, msg, 250);
-	}
-#endif
-	chan_list_unref(tmp, "Digit(s) sent");
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_toggle_echocancel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	const char *channame;
-	struct chan_list *tmp;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn toggle echocancel";
-		e->usage =
-			"Usage: misdn toggle echocancel <channel>\n"
-			"       Toggle EchoCancel on mISDN Channel.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return complete_ch(a);
-	}
-
-	if (a->argc != 4) {
-		return CLI_SHOWUSAGE;
-	}
-
-	channame = a->argv[3];
-
-	ast_cli(a->fd, "Toggling EchoCancel on %s\n", channame);
-
-	tmp = get_chan_by_ast_name(channame);
-	if (!tmp) {
-		ast_cli(a->fd, "Toggling EchoCancel %s failed Channel does not exist\n", channame);
-		return CLI_SUCCESS;
-	}
-
-	tmp->toggle_ec = tmp->toggle_ec ? 0 : 1;
-
-	if (tmp->toggle_ec) {
-#ifdef MISDN_1_2
-		update_pipeline_config(tmp->bc);
-#else
-		update_ec_config(tmp->bc);
-#endif
-		manager_ec_enable(tmp->bc);
-	} else {
-		manager_ec_disable(tmp->bc);
-	}
-	chan_list_unref(tmp, "Done toggling echo cancel");
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_misdn_send_display(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	const char *channame;
-	const char *msg;
-	struct chan_list *tmp;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "misdn send display";
-		e->usage =
-			"Usage: misdn send display <channel> \"<msg>\" \n"
-			"       Send <msg> to <channel> as Display Message\n"
-			"       when channel is a mISDN channel\n";
-		return NULL;
-	case CLI_GENERATE:
-		return complete_ch(a);
-	}
-
-	if (a->argc != 5) {
-		return CLI_SHOWUSAGE;
-	}
-
-	channame = a->argv[3];
-	msg = a->argv[4];
-
-	ast_cli(a->fd, "Sending %s to %s\n", msg, channame);
-
-	tmp = get_chan_by_ast_name(channame);
-	if (tmp && tmp->bc) {
-		ast_copy_string(tmp->bc->display, msg, sizeof(tmp->bc->display));
-		misdn_lib_send_event(tmp->bc, EVENT_INFORMATION);
-		chan_list_unref(tmp, "Done sending display");
-	} else {
-		if (tmp) {
-			chan_list_unref(tmp, "Display failed");
-		}
-		ast_cli(a->fd, "No such channel %s\n", channame);
-		return CLI_SUCCESS;
-	}
-
-	return CLI_SUCCESS;
-}
-
-static char *complete_ch(struct ast_cli_args *a)
-{
-	return ast_complete_channels(a->line, a->word, a->pos, a->n, 3);
-}
-
-static char *complete_debug_port(struct ast_cli_args *a)
-{
-	if (a->n) {
-		return NULL;
-	}
-
-	switch (a->pos) {
-	case 4:
-		if (a->word[0] == 'p') {
-			return ast_strdup("port");
-		} else if (a->word[0] == 'o') {
-			return ast_strdup("only");
-		}
-		break;
-	case 6:
-		if (a->word[0] == 'o') {
-			return ast_strdup("only");
-		}
-		break;
-	}
-	return NULL;
-}
-
-static char *complete_show_config(struct ast_cli_args *a)
-{
-	char buffer[BUFFERSIZE];
-	enum misdn_cfg_elements elem;
-	int wordlen = strlen(a->word);
-	int which = 0;
-	int port = 0;
-
-	switch (a->pos) {
-	case 3:
-		if ((!strncmp(a->word, "description", wordlen)) && (++which > a->n)) {
-			return ast_strdup("description");
-		}
-		if ((!strncmp(a->word, "descriptions", wordlen)) && (++which > a->n)) {
-			return ast_strdup("descriptions");
-		}
-		if ((!strncmp(a->word, "0", wordlen)) && (++which > a->n)) {
-			return ast_strdup("0");
-		}
-		while ((port = misdn_cfg_get_next_port(port)) != -1) {
-			snprintf(buffer, sizeof(buffer), "%d", port);
-			if ((!strncmp(a->word, buffer, wordlen)) && (++which > a->n)) {
-				return ast_strdup(buffer);
-			}
-		}
-		break;
-	case 4:
-		if (strstr(a->line, "description ")) {
-			for (elem = MISDN_CFG_FIRST + 1; elem < MISDN_GEN_LAST; ++elem) {
-				if ((elem == MISDN_CFG_LAST) || (elem == MISDN_GEN_FIRST)) {
-					continue;
-				}
-				misdn_cfg_get_name(elem, buffer, sizeof(buffer));
-				if (!wordlen || !strncmp(a->word, buffer, wordlen)) {
-					if (++which > a->n) {
-						return ast_strdup(buffer);
-					}
-				}
-			}
-		} else if (strstr(a->line, "descriptions ")) {
-			if ((!wordlen || !strncmp(a->word, "general", wordlen)) && (++which > a->n)) {
-				return ast_strdup("general");
-			}
-			if ((!wordlen || !strncmp(a->word, "ports", wordlen)) && (++which > a->n)) {
-				return ast_strdup("ports");
-			}
-		}
-		break;
-	}
-	return NULL;
-}
-
-static struct ast_cli_entry chan_misdn_clis[] = {
-/* *INDENT-OFF* */
-	AST_CLI_DEFINE(handle_cli_misdn_port_block,        "Block the given port"),
-	AST_CLI_DEFINE(handle_cli_misdn_port_down,         "Try to deactivate the L1 on the given port"),
-	AST_CLI_DEFINE(handle_cli_misdn_port_unblock,      "Unblock the given port"),
-	AST_CLI_DEFINE(handle_cli_misdn_port_up,           "Try to establish L1 on the given port"),
-	AST_CLI_DEFINE(handle_cli_misdn_reload,            "Reload internal mISDN config, read from the config file"),
-	AST_CLI_DEFINE(handle_cli_misdn_restart_pid,       "Restart the given pid"),
-	AST_CLI_DEFINE(handle_cli_misdn_restart_port,      "Restart the given port"),
-	AST_CLI_DEFINE(handle_cli_misdn_show_channel,      "Show an internal mISDN channel"),
-	AST_CLI_DEFINE(handle_cli_misdn_show_channels,     "Show the internal mISDN channel list"),
-	AST_CLI_DEFINE(handle_cli_misdn_show_config,       "Show internal mISDN config, read from the config file"),
-	AST_CLI_DEFINE(handle_cli_misdn_show_port,         "Show detailed information for given port"),
-	AST_CLI_DEFINE(handle_cli_misdn_show_ports_stats,  "Show mISDNs channel's call statistics per port"),
-	AST_CLI_DEFINE(handle_cli_misdn_show_stacks,       "Show internal mISDN stack_list"),
-	AST_CLI_DEFINE(handle_cli_misdn_send_facility,     "Sends a Facility Message to the mISDN Channel"),
-	AST_CLI_DEFINE(handle_cli_misdn_send_digit,        "Send DTMF digit to mISDN Channel"),
-	AST_CLI_DEFINE(handle_cli_misdn_send_display,      "Send Text to mISDN Channel"),
-	AST_CLI_DEFINE(handle_cli_misdn_send_restart,      "Send a restart for every bchannel on the given port"),
-	AST_CLI_DEFINE(handle_cli_misdn_set_crypt_debug,   "Set CryptDebuglevel of chan_misdn, at the moment, level={1,2}"),
-	AST_CLI_DEFINE(handle_cli_misdn_set_debug,         "Set Debuglevel of chan_misdn"),
-	AST_CLI_DEFINE(handle_cli_misdn_set_tics,          "???"),
-	AST_CLI_DEFINE(handle_cli_misdn_toggle_echocancel, "Toggle EchoCancel on mISDN Channel"),
-/* *INDENT-ON* */
-};
-
-/*! \brief Updates caller ID information from config */
-static void update_config(struct chan_list *ch)
-{
-	struct ast_channel *ast;
-	struct misdn_bchannel *bc;
-	int port;
-	int hdlc = 0;
-	int pres;
-	int screen;
-
-	if (!ch) {
-		ast_log(LOG_WARNING, "Cannot configure without chanlist\n");
-		return;
-	}
-
-	ast = ch->ast;
-	bc = ch->bc;
-	if (! ast || ! bc) {
-		ast_log(LOG_WARNING, "Cannot configure without ast || bc\n");
-		return;
-	}
-
-	port = bc->port;
-
-	chan_misdn_log(7, port, "update_config: Getting Config\n");
-
-	misdn_cfg_get(port, MISDN_CFG_HDLC, &hdlc, sizeof(int));
-	if (hdlc) {
-		switch (bc->capability) {
-		case INFO_CAPABILITY_DIGITAL_UNRESTRICTED:
-		case INFO_CAPABILITY_DIGITAL_RESTRICTED:
-			chan_misdn_log(1, bc->port, " --> CONF HDLC\n");
-			bc->hdlc = 1;
-			break;
-		}
-	}
-
-
-	misdn_cfg_get(port, MISDN_CFG_PRES, &pres, sizeof(pres));
-	misdn_cfg_get(port, MISDN_CFG_SCREEN, &screen, sizeof(screen));
-	chan_misdn_log(2, port, " --> pres: %d screen: %d\n", pres, screen);
-
-	if (pres < 0 || screen < 0) {
-		chan_misdn_log(2, port, " --> pres: %x\n", ast_channel_connected(ast)->id.number.presentation);
-
-		bc->caller.presentation = ast_to_misdn_pres(ast_channel_connected(ast)->id.number.presentation);
-		chan_misdn_log(2, port, " --> PRES: %s(%d)\n", misdn_to_str_pres(bc->caller.presentation), bc->caller.presentation);
-
-		bc->caller.screening = ast_to_misdn_screen(ast_channel_connected(ast)->id.number.presentation);
-		chan_misdn_log(2, port, " --> SCREEN: %s(%d)\n", misdn_to_str_screen(bc->caller.screening), bc->caller.screening);
-	} else {
-		bc->caller.screening = screen;
-		bc->caller.presentation = pres;
-	}
-}
-
-
-static void config_jitterbuffer(struct chan_list *ch)
-{
-	struct misdn_bchannel *bc = ch->bc;
-	int len = ch->jb_len;
-	int threshold = ch->jb_upper_threshold;
-
-	chan_misdn_log(5, bc->port, "config_jb: Called\n");
-
-	if (!len) {
-		chan_misdn_log(1, bc->port, "config_jb: Deactivating Jitterbuffer\n");
-		bc->nojitter = 1;
-	} else {
-		if (len <= 100 || len > 8000) {
-			chan_misdn_log(0, bc->port, "config_jb: Jitterbuffer out of Bounds, setting to 1000\n");
-			len = 1000;
-		}
-
-		if (threshold > len) {
-			chan_misdn_log(0, bc->port, "config_jb: Jitterbuffer Threshold > Jitterbuffer setting to Jitterbuffer -1\n");
-		}
-
-		if (ch->jb) {
-			cb_log(0, bc->port, "config_jb: We've got a Jitterbuffer Already on this port.\n");
-			misdn_jb_destroy(ch->jb);
-			ch->jb = NULL;
-		}
-
-		ch->jb = misdn_jb_init(len, threshold);
-
-		if (!ch->jb) {
-			bc->nojitter = 1;
-		}
-	}
-}
-
-
-void debug_numtype(int port, int numtype, char *type)
-{
-	switch (numtype) {
-	case NUMTYPE_UNKNOWN:
-		chan_misdn_log(2, port, " --> %s: Unknown\n", type);
-		break;
-	case NUMTYPE_INTERNATIONAL:
-		chan_misdn_log(2, port, " --> %s: International\n", type);
-		break;
-	case NUMTYPE_NATIONAL:
-		chan_misdn_log(2, port, " --> %s: National\n", type);
-		break;
-	case NUMTYPE_NETWORK_SPECIFIC:
-		chan_misdn_log(2, port, " --> %s: Network Specific\n", type);
-		break;
-	case NUMTYPE_SUBSCRIBER:
-		chan_misdn_log(2, port, " --> %s: Subscriber\n", type);
-		break;
-	case NUMTYPE_ABBREVIATED:
-		chan_misdn_log(2, port, " --> %s: Abbreviated\n", type);
-		break;
-		/* Maybe we should cut off the prefix if present ? */
-	default:
-		chan_misdn_log(0, port, " --> !!!! Wrong dialplan setting, please see the misdn.conf sample file\n ");
-		break;
-	}
-}
-
-
-#ifdef MISDN_1_2
-static int update_pipeline_config(struct misdn_bchannel *bc)
-{
-	int ec;
-
-	misdn_cfg_get(bc->port, MISDN_CFG_PIPELINE, bc->pipeline, sizeof(bc->pipeline));
-
-	if (*bc->pipeline) {
-		return 0;
-	}
-
-	misdn_cfg_get(bc->port, MISDN_CFG_ECHOCANCEL, &ec, sizeof(ec));
-	if (ec == 1) {
-		ast_copy_string(bc->pipeline, "mg2ec", sizeof(bc->pipeline));
-	} else if (ec > 1) {
-		snprintf(bc->pipeline, sizeof(bc->pipeline), "mg2ec(deftaps=%d)", ec);
-	}
-
-	return 0;
-}
-#else
-static int update_ec_config(struct misdn_bchannel *bc)
-{
-	int ec;
-	int port = bc->port;
-
-	misdn_cfg_get(port, MISDN_CFG_ECHOCANCEL, &ec, sizeof(ec));
-
-	if (ec == 1) {
-		bc->ec_enable = 1;
-	} else if (ec > 1) {
-		bc->ec_enable = 1;
-		bc->ec_deftaps = ec;
-	}
-
-	return 0;
-}
-#endif
-
-
-static int read_config(struct chan_list *ch)
-{
-	struct ast_channel *ast;
-	struct misdn_bchannel *bc;
-	int port;
-	int hdlc = 0;
-	char lang[BUFFERSIZE + 1];
-	char faxdetect[BUFFERSIZE + 1];
-	char buf[256];
-	char buf2[256];
-	ast_group_t pg;
-	ast_group_t cg;
-	struct ast_namedgroups *npg;
-	struct ast_namedgroups *ncg;
-	struct ast_str *tmp_str;
-
-	if (!ch) {
-		ast_log(LOG_WARNING, "Cannot configure without chanlist\n");
-		return -1;
-	}
-
-	ast = ch->ast;
-	bc = ch->bc;
-	if (! ast || ! bc) {
-		ast_log(LOG_WARNING, "Cannot configure without ast || bc\n");
-		return -1;
-	}
-
-	port = bc->port;
-	chan_misdn_log(1, port, "read_config: Getting Config\n");
-
-	misdn_cfg_get(port, MISDN_CFG_LANGUAGE, lang, sizeof(lang));
-	ast_channel_lock(ast);
-	ast_channel_language_set(ast, lang);
-	ast_channel_unlock(ast);
-
-	misdn_cfg_get(port, MISDN_CFG_MUSICCLASS, ch->mohinterpret, sizeof(ch->mohinterpret));
-
-	misdn_cfg_get(port, MISDN_CFG_TXGAIN, &bc->txgain, sizeof(bc->txgain));
-	misdn_cfg_get(port, MISDN_CFG_RXGAIN, &bc->rxgain, sizeof(bc->rxgain));
-
-	misdn_cfg_get(port, MISDN_CFG_INCOMING_EARLY_AUDIO, &ch->incoming_early_audio, sizeof(ch->incoming_early_audio));
-
-	misdn_cfg_get(port, MISDN_CFG_SENDDTMF, &bc->send_dtmf, sizeof(bc->send_dtmf));
-
-	misdn_cfg_get(port, MISDN_CFG_ASTDTMF, &ch->ast_dsp, sizeof(int));
-	if (ch->ast_dsp) {
-		ch->ignore_dtmf = 1;
-	}
-
-	misdn_cfg_get(port, MISDN_CFG_NEED_MORE_INFOS, &bc->need_more_infos, sizeof(bc->need_more_infos));
-	misdn_cfg_get(port, MISDN_CFG_NTTIMEOUT, &ch->nttimeout, sizeof(ch->nttimeout));
-
-	misdn_cfg_get(port, MISDN_CFG_NOAUTORESPOND_ON_SETUP, &ch->noautorespond_on_setup, sizeof(ch->noautorespond_on_setup));
-
-	misdn_cfg_get(port, MISDN_CFG_FAR_ALERTING, &ch->far_alerting, sizeof(ch->far_alerting));
-
-	misdn_cfg_get(port, MISDN_CFG_ALLOWED_BEARERS, &ch->allowed_bearers, sizeof(ch->allowed_bearers));
-
-  	misdn_cfg_get(port, MISDN_CFG_FAXDETECT, faxdetect, sizeof(faxdetect));
-
-	misdn_cfg_get(port, MISDN_CFG_HDLC, &hdlc, sizeof(hdlc));
-	if (hdlc) {
-		switch (bc->capability) {
-		case INFO_CAPABILITY_DIGITAL_UNRESTRICTED:
-		case INFO_CAPABILITY_DIGITAL_RESTRICTED:
-			chan_misdn_log(1, bc->port, " --> CONF HDLC\n");
-			bc->hdlc = 1;
-			break;
-		}
-
-	}
-	/*Initialize new Jitterbuffer*/
-	misdn_cfg_get(port, MISDN_CFG_JITTERBUFFER, &ch->jb_len, sizeof(ch->jb_len));
-	misdn_cfg_get(port, MISDN_CFG_JITTERBUFFER_UPPER_THRESHOLD, &ch->jb_upper_threshold, sizeof(ch->jb_upper_threshold));
-
-	config_jitterbuffer(ch);
-
-	misdn_cfg_get(bc->port, MISDN_CFG_CONTEXT, ch->context, sizeof(ch->context));
-
-	ast_channel_lock(ast);
-	ast_channel_context_set(ast, ch->context);
-	ast_channel_unlock(ast);
-
-#ifdef MISDN_1_2
-	update_pipeline_config(bc);
-#else
-	update_ec_config(bc);
-#endif
-
-	misdn_cfg_get(bc->port, MISDN_CFG_EARLY_BCONNECT, &bc->early_bconnect, sizeof(bc->early_bconnect));
-
-	misdn_cfg_get(port, MISDN_CFG_DISPLAY_CONNECTED, &bc->display_connected, sizeof(bc->display_connected));
-	misdn_cfg_get(port, MISDN_CFG_DISPLAY_SETUP, &bc->display_setup, sizeof(bc->display_setup));
-	misdn_cfg_get(port, MISDN_CFG_OUTGOING_COLP, &bc->outgoing_colp, sizeof(bc->outgoing_colp));
-
-	misdn_cfg_get(port, MISDN_CFG_PICKUPGROUP, &pg, sizeof(pg));
-	misdn_cfg_get(port, MISDN_CFG_CALLGROUP, &cg, sizeof(cg));
-	chan_misdn_log(5, port, " --> * CallGrp:%s PickupGrp:%s\n", ast_print_group(buf, sizeof(buf), cg), ast_print_group(buf2, sizeof(buf2), pg));
-	ast_channel_lock(ast);
-	ast_channel_pickupgroup_set(ast, pg);
-	ast_channel_callgroup_set(ast, cg);
-	ast_channel_unlock(ast);
-
-	misdn_cfg_get(port, MISDN_CFG_NAMEDPICKUPGROUP, &npg, sizeof(npg));
-	misdn_cfg_get(port, MISDN_CFG_NAMEDCALLGROUP, &ncg, sizeof(ncg));
-
-	tmp_str = ast_str_create(1024);
-	if (tmp_str) {
-		chan_misdn_log(5, port, " --> * NamedCallGrp:%s\n", ast_print_namedgroups(&tmp_str, ncg));
-		ast_str_reset(tmp_str);
-		chan_misdn_log(5, port, " --> * NamedPickupGrp:%s\n", ast_print_namedgroups(&tmp_str, npg));
-		ast_free(tmp_str);
-	}
-
-	ast_channel_lock(ast);
-	ast_channel_named_pickupgroups_set(ast, npg);
-	ast_channel_named_callgroups_set(ast, ncg);
-	ast_channel_unlock(ast);
-
-	if (ch->originator == ORG_AST) {
-		char callerid[BUFFERSIZE + 1];
-
-		/* ORIGINATOR Asterisk (outgoing call) */
-
-		misdn_cfg_get(port, MISDN_CFG_TE_CHOOSE_CHANNEL, &(bc->te_choose_channel), sizeof(bc->te_choose_channel));
-
- 		if (strstr(faxdetect, "outgoing") || strstr(faxdetect, "both")) {
- 			ch->faxdetect = strstr(faxdetect, "nojump") ? 2 : 1;
- 		}
-
-		misdn_cfg_get(port, MISDN_CFG_CALLERID, callerid, sizeof(callerid));
-		if (!ast_strlen_zero(callerid)) {
-			char *cid_name = NULL;
-			char *cid_num = NULL;
-
-			ast_callerid_parse(callerid, &cid_name, &cid_num);
-			if (cid_name) {
-				ast_copy_string(bc->caller.name, cid_name, sizeof(bc->caller.name));
-			} else {
-				bc->caller.name[0] = '\0';
-			}
-			if (cid_num) {
-				ast_copy_string(bc->caller.number, cid_num, sizeof(bc->caller.number));
-			} else {
-				bc->caller.number[0] = '\0';
-			}
-			chan_misdn_log(1, port, " --> * Setting caller to \"%s\" <%s>\n", bc->caller.name, bc->caller.number);
-		}
-
-		misdn_cfg_get(port, MISDN_CFG_DIALPLAN, &bc->dialed.number_type, sizeof(bc->dialed.number_type));
-		bc->dialed.number_plan = NUMPLAN_ISDN;
-		debug_numtype(port, bc->dialed.number_type, "TON");
-
-		ch->overlap_dial = 0;
-	} else {
-		/* ORIGINATOR MISDN (incoming call) */
-
- 		if (strstr(faxdetect, "incoming") || strstr(faxdetect, "both")) {
- 			ch->faxdetect = (strstr(faxdetect, "nojump")) ? 2 : 1;
- 		}
-
-		/* Add configured prefix to caller.number */
-		misdn_add_number_prefix(bc->port, bc->caller.number_type, bc->caller.number, sizeof(bc->caller.number));
-
-		if (ast_strlen_zero(bc->dialed.number) && !ast_strlen_zero(bc->keypad)) {
-			ast_copy_string(bc->dialed.number, bc->keypad, sizeof(bc->dialed.number));
-		}
-
-		/* Add configured prefix to dialed.number */
-		misdn_add_number_prefix(bc->port, bc->dialed.number_type, bc->dialed.number, sizeof(bc->dialed.number));
-
-		ast_channel_lock(ast);
-		ast_channel_exten_set(ast, bc->dialed.number);
-		ast_channel_unlock(ast);
-
-		misdn_cfg_get(bc->port, MISDN_CFG_OVERLAP_DIAL, &ch->overlap_dial, sizeof(ch->overlap_dial));
-		ast_mutex_init(&ch->overlap_tv_lock);
-	} /* ORIG MISDN END */
-
-	misdn_cfg_get(port, MISDN_CFG_INCOMING_CALLERID_TAG, bc->incoming_cid_tag, sizeof(bc->incoming_cid_tag));
-	if (!ast_strlen_zero(bc->incoming_cid_tag)) {
-		chan_misdn_log(1, port, " --> * Setting incoming caller id tag to \"%s\"\n", bc->incoming_cid_tag);
-	}
-	ch->overlap_dial_task = -1;
-
-	if (ch->faxdetect  || ch->ast_dsp) {
-		misdn_cfg_get(port, MISDN_CFG_FAXDETECT_TIMEOUT, &ch->faxdetect_timeout, sizeof(ch->faxdetect_timeout));
-		if (!ch->dsp) {
-			ch->dsp = ast_dsp_new();
-		}
-		if (ch->dsp) {
-			ast_dsp_set_features(ch->dsp, DSP_FEATURE_DIGIT_DETECT | (ch->faxdetect ? DSP_FEATURE_FAX_DETECT : 0));
-		}
-	}
-
-	/* AOCD initialization */
-	bc->AOCDtype = Fac_None;
-
-	return 0;
-}
-
-/*!
- * \internal
- * \brief Send a connected line update to the other channel
- *
- * \param ast Current Asterisk channel
- * \param id Party id information to send to the other side
- * \param source Why are we sending this update
- * \param cid_tag User tag to apply to the party id.
- */
-static void misdn_queue_connected_line_update(struct ast_channel *ast, const struct misdn_party_id *id, enum AST_CONNECTED_LINE_UPDATE_SOURCE source, char *cid_tag)
-{
-	struct ast_party_connected_line connected;
-	struct ast_set_party_connected_line update_connected;
-
-	ast_party_connected_line_init(&connected);
-	memset(&update_connected, 0, sizeof(update_connected));
-	update_connected.id.number = 1;
-	connected.id.number.valid = 1;
-	connected.id.number.str = (char *) id->number;
-	connected.id.number.plan = misdn_to_ast_ton(id->number_type)
-		| misdn_to_ast_plan(id->number_plan);
-	connected.id.number.presentation = misdn_to_ast_pres(id->presentation)
-		| misdn_to_ast_screen(id->screening);
-
-	/*
-	 * Make sure that any earlier private connected id
-	 * representation at the remote end is invalidated
-	 */
-	ast_set_party_id_all(&update_connected.priv);
-
-	connected.id.tag = cid_tag;
-	connected.source = source;
-	ast_channel_queue_connected_line_update(ast, &connected, &update_connected);
-}
-
-/*!
- * \internal
- * \brief Update the caller id party on this channel.
- *
- * \param ast Current Asterisk channel
- * \param id Remote party id information to update.
- * \param cid_tag User tag to apply to the party id.
- */
-static void misdn_update_caller_id(struct ast_channel *ast, const struct misdn_party_id *id, char *cid_tag)
-{
-	struct ast_party_caller caller;
-	struct ast_set_party_caller update_caller;
-
-	memset(&update_caller, 0, sizeof(update_caller));
-	update_caller.id.number = 1;
-	update_caller.ani.number = 1;
-
-	ast_channel_lock(ast);
-	ast_party_caller_set_init(&caller, ast_channel_caller(ast));
-
-	caller.id.number.valid = 1;
-	caller.id.number.str = (char *) id->number;
-	caller.id.number.plan = misdn_to_ast_ton(id->number_type)
-		| misdn_to_ast_plan(id->number_plan);
-	caller.id.number.presentation = misdn_to_ast_pres(id->presentation)
-		| misdn_to_ast_screen(id->screening);
-
-	caller.ani.number = caller.id.number;
-
-	caller.id.tag = cid_tag;
-	caller.ani.tag = cid_tag;
-
-	ast_channel_set_caller_event(ast, &caller, &update_caller);
-	ast_channel_unlock(ast);
-}
-
-/*!
- * \internal
- * \brief Update the remote party id information.
- *
- * \param ast Current Asterisk channel
- * \param id Remote party id information to update.
- * \param source Why are we sending this update
- * \param cid_tag User tag to apply to the party id.
- */
-static void misdn_update_remote_party(struct ast_channel *ast, const struct misdn_party_id *id, enum AST_CONNECTED_LINE_UPDATE_SOURCE source, char *cid_tag)
-{
-	misdn_update_caller_id(ast, id, cid_tag);
-	misdn_queue_connected_line_update(ast, id, source, cid_tag);
-}
-
-/*!
- * \internal
- * \brief Get the connected line information out of the Asterisk channel.
- *
- * \param ast Current Asterisk channel
- * \param bc Associated B channel
- * \param originator Who originally created this channel. ORG_AST or ORG_MISDN
- */
-static void misdn_get_connected_line(struct ast_channel *ast, struct misdn_bchannel *bc, int originator)
-{
-	int number_type;
-	struct ast_party_id connected_id = ast_channel_connected_effective_id(ast);
-
-	if (originator == ORG_MISDN) {
-		/* ORIGINATOR MISDN (incoming call) */
-
-		ast_copy_string(bc->connected.name,
-			S_COR(connected_id.name.valid, connected_id.name.str, ""),
-			sizeof(bc->connected.name));
-		if (connected_id.number.valid) {
-			ast_copy_string(bc->connected.number, S_OR(connected_id.number.str, ""),
-				sizeof(bc->connected.number));
-			bc->connected.presentation = ast_to_misdn_pres(connected_id.number.presentation);
-			bc->connected.screening = ast_to_misdn_screen(connected_id.number.presentation);
-			bc->connected.number_type = ast_to_misdn_ton(connected_id.number.plan);
-			bc->connected.number_plan = ast_to_misdn_plan(connected_id.number.plan);
-		} else {
-			bc->connected.number[0] = '\0';
-			bc->connected.presentation = 0;/* Allowed */
-			bc->connected.screening = 0;/* Unscreened */
-			bc->connected.number_type = NUMTYPE_UNKNOWN;
-			bc->connected.number_plan = NUMPLAN_UNKNOWN;
-		}
-
-		misdn_cfg_get(bc->port, MISDN_CFG_CPNDIALPLAN, &number_type, sizeof(number_type));
-		if (0 <= number_type) {
-			/* Force us to send in CONNECT message */
-			bc->connected.number_type = number_type;
-			bc->connected.number_plan = NUMPLAN_ISDN;
-		}
-		debug_numtype(bc->port, bc->connected.number_type, "CTON");
-	} else {
-		/* ORIGINATOR Asterisk (outgoing call) */
-
-		ast_copy_string(bc->caller.name,
-			S_COR(connected_id.name.valid, connected_id.name.str, ""),
-			sizeof(bc->caller.name));
-		if (connected_id.number.valid) {
-			ast_copy_string(bc->caller.number, S_OR(connected_id.number.str, ""),
-				sizeof(bc->caller.number));
-			bc->caller.presentation = ast_to_misdn_pres(connected_id.number.presentation);
-			bc->caller.screening = ast_to_misdn_screen(connected_id.number.presentation);
-			bc->caller.number_type = ast_to_misdn_ton(connected_id.number.plan);
-			bc->caller.number_plan = ast_to_misdn_plan(connected_id.number.plan);
-		} else {
-			bc->caller.number[0] = '\0';
-			bc->caller.presentation = 0;/* Allowed */
-			bc->caller.screening = 0;/* Unscreened */
-			bc->caller.number_type = NUMTYPE_UNKNOWN;
-			bc->caller.number_plan = NUMPLAN_UNKNOWN;
-		}
-
-		misdn_cfg_get(bc->port, MISDN_CFG_LOCALDIALPLAN, &number_type, sizeof(number_type));
-		if (0 <= number_type) {
-			/* Force us to send in SETUP message */
-			bc->caller.number_type = number_type;
-			bc->caller.number_plan = NUMPLAN_ISDN;
-		}
-		debug_numtype(bc->port, bc->caller.number_type, "LTON");
-	}
-}
-
-/*!
- * \internal
- * \brief Notify peer that the connected line has changed.
- *
- * \param ast Current Asterisk channel
- * \param bc Associated B channel
- * \param originator Who originally created this channel. ORG_AST or ORG_MISDN
- */
-static void misdn_update_connected_line(struct ast_channel *ast, struct misdn_bchannel *bc, int originator)
-{
-	struct chan_list *ch;
-
-	misdn_get_connected_line(ast, bc, originator);
-	if (originator == ORG_MISDN) {
-		bc->redirecting.to = bc->connected;
-	} else {
-		bc->redirecting.to = bc->caller;
-	}
-	switch (bc->outgoing_colp) {
-	case 1:/* restricted */
-		bc->redirecting.to.presentation = 1;/* restricted */
-		break;
-	case 2:/* blocked */
-		/* Don't tell the remote party that the call was transferred. */
-		return;
-	default:
-		break;
-	}
-
-	ch = MISDN_ASTERISK_TECH_PVT(ast);
-	if (ch->state == MISDN_CONNECTED
-		|| originator != ORG_MISDN) {
-		int is_ptmp;
-
-		is_ptmp = !misdn_lib_is_ptp(bc->port);
-		if (is_ptmp) {
-			/*
-			 * We should not send these messages to the network if we are
-			 * the CPE side since phones do not transfer calls within
-			 * themselves.  Well... If you consider handing the handset to
-			 * someone else a transfer then how is the network to know?
-			 */
-			if (!misdn_lib_port_is_nt(bc->port)) {
-				return;
-			}
-			if (ch->state != MISDN_CONNECTED) {
-				/* Send NOTIFY(Nie(transfer-active), RDNie(redirecting.to data)) */
-				bc->redirecting.to_changed = 1;
-				bc->notify_description_code = mISDN_NOTIFY_CODE_CALL_TRANSFER_ACTIVE;
-				misdn_lib_send_event(bc, EVENT_NOTIFY);
-#if defined(AST_MISDN_ENHANCEMENTS)
-			} else {
-				/* Send FACILITY(Fie(RequestSubaddress), Nie(transfer-active), RDNie(redirecting.to data)) */
-				bc->redirecting.to_changed = 1;
-				bc->notify_description_code = mISDN_NOTIFY_CODE_CALL_TRANSFER_ACTIVE;
-				bc->fac_out.Function = Fac_RequestSubaddress;
-				bc->fac_out.u.RequestSubaddress.InvokeID = ++misdn_invoke_id;
-
-				/* Send message */
-				print_facility(&bc->fac_out, bc);
-				misdn_lib_send_event(bc, EVENT_FACILITY);
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-			}
-#if defined(AST_MISDN_ENHANCEMENTS)
-		} else {
-			/* Send FACILITY(Fie(EctInform(transfer-active, redirecting.to data))) */
-			bc->fac_out.Function = Fac_EctInform;
-			bc->fac_out.u.EctInform.InvokeID = ++misdn_invoke_id;
-			bc->fac_out.u.EctInform.Status = 1;/* active */
-			bc->fac_out.u.EctInform.RedirectionPresent = 1;/* Must be present when status is active */
-			misdn_PresentedNumberUnscreened_fill(&bc->fac_out.u.EctInform.Redirection,
-				&bc->redirecting.to);
-
-			/* Send message */
-			print_facility(&bc->fac_out, bc);
-			misdn_lib_send_event(bc, EVENT_FACILITY);
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-		}
-	}
-}
-
-/*!
- * \internal
- * \brief Copy the redirecting information out of the Asterisk channel
- *
- * \param bc Associated B channel
- * \param ast Current Asterisk channel
- */
-static void misdn_copy_redirecting_from_ast(struct misdn_bchannel *bc, struct ast_channel *ast)
-{
-	struct ast_party_id from_id = ast_channel_redirecting_effective_from(ast);
-	struct ast_party_id to_id = ast_channel_redirecting_effective_to(ast);
-
-	ast_copy_string(bc->redirecting.from.name,
-		S_COR(from_id.name.valid, from_id.name.str, ""),
-		sizeof(bc->redirecting.from.name));
-	if (from_id.number.valid) {
-		ast_copy_string(bc->redirecting.from.number, S_OR(from_id.number.str, ""),
-			sizeof(bc->redirecting.from.number));
-		bc->redirecting.from.presentation = ast_to_misdn_pres(from_id.number.presentation);
-		bc->redirecting.from.screening = ast_to_misdn_screen(from_id.number.presentation);
-		bc->redirecting.from.number_type = ast_to_misdn_ton(from_id.number.plan);
-		bc->redirecting.from.number_plan = ast_to_misdn_plan(from_id.number.plan);
-	} else {
-		bc->redirecting.from.number[0] = '\0';
-		bc->redirecting.from.presentation = 0;/* Allowed */
-		bc->redirecting.from.screening = 0;/* Unscreened */
-		bc->redirecting.from.number_type = NUMTYPE_UNKNOWN;
-		bc->redirecting.from.number_plan = NUMPLAN_UNKNOWN;
-	}
-
-	ast_copy_string(bc->redirecting.to.name,
-		S_COR(to_id.name.valid, to_id.name.str, ""),
-		sizeof(bc->redirecting.to.name));
-	if (to_id.number.valid) {
-		ast_copy_string(bc->redirecting.to.number, S_OR(to_id.number.str, ""),
-			sizeof(bc->redirecting.to.number));
-		bc->redirecting.to.presentation = ast_to_misdn_pres(to_id.number.presentation);
-		bc->redirecting.to.screening = ast_to_misdn_screen(to_id.number.presentation);
-		bc->redirecting.to.number_type = ast_to_misdn_ton(to_id.number.plan);
-		bc->redirecting.to.number_plan = ast_to_misdn_plan(to_id.number.plan);
-	} else {
-		bc->redirecting.to.number[0] = '\0';
-		bc->redirecting.to.presentation = 0;/* Allowed */
-		bc->redirecting.to.screening = 0;/* Unscreened */
-		bc->redirecting.to.number_type = NUMTYPE_UNKNOWN;
-		bc->redirecting.to.number_plan = NUMPLAN_UNKNOWN;
-	}
-
-	bc->redirecting.reason = ast_to_misdn_reason(ast_channel_redirecting(ast)->reason.code);
-	bc->redirecting.count = ast_channel_redirecting(ast)->count;
-}
-
-/*!
- * \internal
- * \brief Copy the redirecting info into the Asterisk channel
- *
- * \param ast Current Asterisk channel
- * \param redirect Associated B channel redirecting info
- * \param tag Caller ID tag to set in the redirecting party fields
- */
-static void misdn_copy_redirecting_to_ast(struct ast_channel *ast, const struct misdn_party_redirecting *redirect, char *tag)
-{
-	struct ast_party_redirecting redirecting;
-	struct ast_set_party_redirecting update_redirecting;
-
-	ast_party_redirecting_set_init(&redirecting, ast_channel_redirecting(ast));
-	memset(&update_redirecting, 0, sizeof(update_redirecting));
-
-	update_redirecting.from.number = 1;
-	redirecting.from.number.valid = 1;
-	redirecting.from.number.str = (char *) redirect->from.number;
-	redirecting.from.number.plan =
-		misdn_to_ast_ton(redirect->from.number_type)
-		| misdn_to_ast_plan(redirect->from.number_plan);
-	redirecting.from.number.presentation =
-		misdn_to_ast_pres(redirect->from.presentation)
-		| misdn_to_ast_screen(redirect->from.screening);
-	redirecting.from.tag = tag;
-
-	update_redirecting.to.number = 1;
-	redirecting.to.number.valid = 1;
-	redirecting.to.number.str = (char *) redirect->to.number;
-	redirecting.to.number.plan =
-		misdn_to_ast_ton(redirect->to.number_type)
-		| misdn_to_ast_plan(redirect->to.number_plan);
-	redirecting.to.number.presentation =
-		misdn_to_ast_pres(redirect->to.presentation)
-		| misdn_to_ast_screen(redirect->to.screening);
-	redirecting.to.tag = tag;
-
-	redirecting.reason.code = misdn_to_ast_reason(redirect->reason);
-	redirecting.count = redirect->count;
-
-	ast_channel_set_redirecting(ast, &redirecting, &update_redirecting);
-}
-
-/*!
- * \internal
- * \brief Notify peer that the redirecting information has changed.
- *
- * \param ast Current Asterisk channel
- * \param bc Associated B channel
- * \param originator Who originally created this channel. ORG_AST or ORG_MISDN
- */
-static void misdn_update_redirecting(struct ast_channel *ast, struct misdn_bchannel *bc, int originator)
-{
-	int is_ptmp;
-
-	misdn_copy_redirecting_from_ast(bc, ast);
-	switch (bc->outgoing_colp) {
-	case 1:/* restricted */
-		bc->redirecting.to.presentation = 1;/* restricted */
-		break;
-	case 2:/* blocked */
-		/* Don't tell the remote party that the call was redirected. */
-		return;
-	default:
-		break;
-	}
-
-	if (originator != ORG_MISDN) {
-		return;
-	}
-
-	is_ptmp = !misdn_lib_is_ptp(bc->port);
-	if (is_ptmp) {
-		/*
-		 * We should not send these messages to the network if we are
-		 * the CPE side since phones do not redirect calls within
-		 * themselves.  Well... If you consider someone else picking up
-		 * the handset a redirection then how is the network to know?
-		 */
-		if (!misdn_lib_port_is_nt(bc->port)) {
-			return;
-		}
-		/* Send NOTIFY(call-is-diverting, redirecting.to data) */
-		bc->redirecting.to_changed = 1;
-		bc->notify_description_code = mISDN_NOTIFY_CODE_CALL_IS_DIVERTING;
-		misdn_lib_send_event(bc, EVENT_NOTIFY);
-#if defined(AST_MISDN_ENHANCEMENTS)
-	} else {
-		int match;	/* TRUE if the dialed number matches the redirecting to number */
-
-		match = (strcmp(ast_channel_exten(ast), bc->redirecting.to.number) == 0) ? 1 : 0;
-		if (!bc->div_leg_3_tx_pending
-			|| !match) {
-			/* Send DivertingLegInformation1 */
-			bc->fac_out.Function = Fac_DivertingLegInformation1;
-			bc->fac_out.u.DivertingLegInformation1.InvokeID = ++misdn_invoke_id;
-			bc->fac_out.u.DivertingLegInformation1.DiversionReason =
-				misdn_to_diversion_reason(bc->redirecting.reason);
-			bc->fac_out.u.DivertingLegInformation1.SubscriptionOption = 2;/* notificationWithDivertedToNr */
-			bc->fac_out.u.DivertingLegInformation1.DivertedToPresent = 1;
-			misdn_PresentedNumberUnscreened_fill(&bc->fac_out.u.DivertingLegInformation1.DivertedTo, &bc->redirecting.to);
-			print_facility(&bc->fac_out, bc);
-			misdn_lib_send_event(bc, EVENT_FACILITY);
-		}
-		bc->div_leg_3_tx_pending = 0;
-
-		/* Send DivertingLegInformation3 */
-		bc->fac_out.Function = Fac_DivertingLegInformation3;
-		bc->fac_out.u.DivertingLegInformation3.InvokeID = ++misdn_invoke_id;
-		bc->fac_out.u.DivertingLegInformation3.PresentationAllowedIndicator =
-			bc->redirecting.to.presentation == 0 ? 1 : 0;
-		print_facility(&bc->fac_out, bc);
-		misdn_lib_send_event(bc, EVENT_FACILITY);
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	}
-}
-
-
-/*****************************/
-/*** AST Indications Start ***/
-/*****************************/
-
-static int misdn_call(struct ast_channel *ast, const char *dest, int timeout)
-{
-	int port = 0;
-	int r;
-	int exceed;
-	int number_type;
-	struct chan_list *ch;
-	struct misdn_bchannel *newbc;
-	char *dest_cp;
-	int append_msn = 0;
-
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(intf);	/* The interface token is discarded. */
-		AST_APP_ARG(ext);	/* extension token */
-		AST_APP_ARG(opts);	/* options token */
-	);
-
-	if (!ast) {
-		ast_log(LOG_WARNING, " --> ! misdn_call called on ast_channel *ast where ast == NULL\n");
-		return -1;
-	}
-
-	if (((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) || !dest) {
-		ast_log(LOG_WARNING, " --> ! misdn_call called on %s, neither down nor reserved (or dest==NULL)\n", ast_channel_name(ast));
-		ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_TEMPORARY_FAILURE);
-		ast_setstate(ast, AST_STATE_DOWN);
-		return -1;
-	}
-
-	ch = MISDN_ASTERISK_TECH_PVT(ast);
-	if (!ch) {
-		ast_log(LOG_WARNING, " --> ! misdn_call called on %s, chan_list *ch==NULL\n", ast_channel_name(ast));
-		ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_TEMPORARY_FAILURE);
-		ast_setstate(ast, AST_STATE_DOWN);
-		return -1;
-	}
-
-	newbc = ch->bc;
-	if (!newbc) {
-		ast_log(LOG_WARNING, " --> ! misdn_call called on %s, newbc==NULL\n", ast_channel_name(ast));
-		ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_TEMPORARY_FAILURE);
-		ast_setstate(ast, AST_STATE_DOWN);
-		return -1;
-	}
-
-	port = newbc->port;
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	if ((ch->peer = misdn_cc_caller_get(ast))) {
-		chan_misdn_log(3, port, " --> Found CC caller data, peer:%s\n",
-			ch->peer->chan ? "available" : "NULL");
-	}
-
-	if (ch->record_id != -1) {
-		struct misdn_cc_record *cc_record;
-
-		/* This is a call completion retry call */
-		AST_LIST_LOCK(&misdn_cc_records_db);
-		cc_record = misdn_cc_find_by_id(ch->record_id);
-		if (!cc_record) {
-			AST_LIST_UNLOCK(&misdn_cc_records_db);
-			ast_log(LOG_WARNING, " --> ! misdn_call called on %s, cc_record==NULL\n", ast_channel_name(ast));
-			ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_TEMPORARY_FAILURE);
-			ast_setstate(ast, AST_STATE_DOWN);
-			return -1;
-		}
-
-		/* Setup calling parameters to retry the call. */
-		newbc->dialed = cc_record->redial.dialed;
-		newbc->caller = cc_record->redial.caller;
-		memset(&newbc->redirecting, 0, sizeof(newbc->redirecting));
-		newbc->capability = cc_record->redial.capability;
-		newbc->hdlc = cc_record->redial.hdlc;
-		newbc->sending_complete = 1;
-
-		if (cc_record->ptp) {
-			newbc->fac_out.Function = Fac_CCBS_T_Call;
-			newbc->fac_out.u.CCBS_T_Call.InvokeID = ++misdn_invoke_id;
-		} else {
-			newbc->fac_out.Function = Fac_CCBSCall;
-			newbc->fac_out.u.CCBSCall.InvokeID = ++misdn_invoke_id;
-			newbc->fac_out.u.CCBSCall.CCBSReference = cc_record->mode.ptmp.reference_id;
-		}
-		AST_LIST_UNLOCK(&misdn_cc_records_db);
-
-		ast_channel_exten_set(ast, newbc->dialed.number);
-
-		chan_misdn_log(1, port, "* Call completion to: %s\n", newbc->dialed.number);
-		chan_misdn_log(2, port, " --> * tech:%s context:%s\n", ast_channel_name(ast), ast_channel_context(ast));
-	} else
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	{
-		struct ast_party_id connected_id = ast_channel_connected_effective_id(ast);
-
-		/*
-		 * dest is ---v
-		 * Dial(mISDN/g:group_name[/extension[/options]])
-		 * Dial(mISDN/port[:preselected_channel][/extension[/options]])
-		 *
-		 * The dial extension could be empty if you are using MISDN_KEYPAD
-		 * to control ISDN provider features.
-		 */
-		dest_cp = ast_strdupa(dest);
-		AST_NONSTANDARD_APP_ARGS(args, dest_cp, '/');
-		if (!args.ext) {
-			args.ext = "";
-		}
-
-		chan_misdn_log(1, port, "* CALL: %s\n", dest);
-		chan_misdn_log(2, port, " --> * dialed:%s tech:%s context:%s\n", args.ext, ast_channel_name(ast), ast_channel_context(ast));
-
-		ast_channel_exten_set(ast, args.ext);
-		ast_copy_string(newbc->dialed.number, args.ext, sizeof(newbc->dialed.number));
-
-		if (ast_strlen_zero(newbc->caller.name)
-			&& connected_id.name.valid
-			&& !ast_strlen_zero(connected_id.name.str)) {
-			ast_copy_string(newbc->caller.name, connected_id.name.str, sizeof(newbc->caller.name));
-			chan_misdn_log(3, port, " --> * set caller:\"%s\" <%s>\n", newbc->caller.name, newbc->caller.number);
-		}
-		if (ast_strlen_zero(newbc->caller.number)
-			&& connected_id.number.valid
-			&& !ast_strlen_zero(connected_id.number.str)) {
-			ast_copy_string(newbc->caller.number, connected_id.number.str, sizeof(newbc->caller.number));
-			chan_misdn_log(3, port, " --> * set caller:\"%s\" <%s>\n", newbc->caller.name, newbc->caller.number);
-		}
-
-		misdn_cfg_get(port, MISDN_CFG_APPEND_MSN_TO_CALLERID_TAG, &append_msn, sizeof(append_msn));
-		if (append_msn) {
-			strncat(newbc->incoming_cid_tag, "_", sizeof(newbc->incoming_cid_tag) - strlen(newbc->incoming_cid_tag) - 1);
-			strncat(newbc->incoming_cid_tag, newbc->caller.number, sizeof(newbc->incoming_cid_tag) - strlen(newbc->incoming_cid_tag) - 1);
-		}
-
-		ast_channel_caller(ast)->id.tag = ast_strdup(newbc->incoming_cid_tag);
-
-		misdn_cfg_get(port, MISDN_CFG_LOCALDIALPLAN, &number_type, sizeof(number_type));
-		if (number_type < 0) {
-			if (connected_id.number.valid) {
-				newbc->caller.number_type = ast_to_misdn_ton(connected_id.number.plan);
-				newbc->caller.number_plan = ast_to_misdn_plan(connected_id.number.plan);
-			} else {
-				newbc->caller.number_type = NUMTYPE_UNKNOWN;
-				newbc->caller.number_plan = NUMPLAN_ISDN;
-			}
-		} else {
-			/* Force us to send in SETUP message */
-			newbc->caller.number_type = number_type;
-			newbc->caller.number_plan = NUMPLAN_ISDN;
-		}
-		debug_numtype(port, newbc->caller.number_type, "LTON");
-
-		newbc->capability = ast_channel_transfercapability(ast);
-		pbx_builtin_setvar_helper(ast, "TRANSFERCAPABILITY", ast_transfercapability2str(newbc->capability));
-		if (ast_channel_transfercapability(ast) == INFO_CAPABILITY_DIGITAL_UNRESTRICTED) {
-			chan_misdn_log(2, port, " --> * Call with flag Digital\n");
-		}
-
-		/* update caller screening and presentation */
-		update_config(ch);
-
-		/* fill in some ies from channel dialplan variables */
-		import_ch(ast, newbc, ch);
-
-		/* Finally The Options Override Everything */
-		if (!ast_strlen_zero(args.opts)) {
-			misdn_set_opt_exec(ast, args.opts);
-		} else {
-			chan_misdn_log(2, port, "NO OPTS GIVEN\n");
-		}
-		if (newbc->set_presentation) {
-			newbc->caller.presentation = newbc->presentation;
-		}
-
-		misdn_copy_redirecting_from_ast(newbc, ast);
-		switch (newbc->outgoing_colp) {
-		case 1:/* restricted */
-		case 2:/* blocked */
-			newbc->redirecting.from.presentation = 1;/* restricted */
-			break;
-		default:
-			break;
-		}
-#if defined(AST_MISDN_ENHANCEMENTS)
-		if (newbc->redirecting.from.number[0] && misdn_lib_is_ptp(port)) {
-			if (newbc->redirecting.count < 1) {
-				newbc->redirecting.count = 1;
-			}
-
-			/* Create DivertingLegInformation2 facility */
-			newbc->fac_out.Function = Fac_DivertingLegInformation2;
-			newbc->fac_out.u.DivertingLegInformation2.InvokeID = ++misdn_invoke_id;
-			newbc->fac_out.u.DivertingLegInformation2.DivertingPresent = 1;
-			misdn_PresentedNumberUnscreened_fill(
-				&newbc->fac_out.u.DivertingLegInformation2.Diverting,
-				&newbc->redirecting.from);
-			switch (newbc->outgoing_colp) {
-			case 2:/* blocked */
-				/* Block the number going out */
-				newbc->fac_out.u.DivertingLegInformation2.Diverting.Type = 1;/* presentationRestricted */
-
-				/* Don't tell about any previous diversions or why for that matter. */
-				newbc->fac_out.u.DivertingLegInformation2.DiversionCounter = 1;
-				newbc->fac_out.u.DivertingLegInformation2.DiversionReason = 0;/* unknown */
-				break;
-			default:
-				newbc->fac_out.u.DivertingLegInformation2.DiversionCounter =
-					newbc->redirecting.count;
-				newbc->fac_out.u.DivertingLegInformation2.DiversionReason =
-					misdn_to_diversion_reason(newbc->redirecting.reason);
-				break;
-			}
-			newbc->fac_out.u.DivertingLegInformation2.OriginalCalledPresent = 0;
-			if (1 < newbc->fac_out.u.DivertingLegInformation2.DiversionCounter) {
-				newbc->fac_out.u.DivertingLegInformation2.OriginalCalledPresent = 1;
-				newbc->fac_out.u.DivertingLegInformation2.OriginalCalled.Type = 2;/* numberNotAvailableDueToInterworking */
-			}
-
-			/*
-			 * Expect a DivertingLegInformation3 to update the COLR of the
-			 * redirecting-to party we are attempting to call now.
-			 */
-			newbc->div_leg_3_rx_wanted = 1;
-		}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	}
-
-	exceed = add_out_calls(port);
-	if (exceed != 0) {
-		char tmp[16];
-
-		snprintf(tmp, sizeof(tmp), "%d", exceed);
-		pbx_builtin_setvar_helper(ast, "MAX_OVERFLOW", tmp);
-		ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_TEMPORARY_FAILURE);
-		ast_setstate(ast, AST_STATE_DOWN);
-		return -1;
-	}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	if (newbc->fac_out.Function != Fac_None) {
-		print_facility(&newbc->fac_out, newbc);
-	}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	r = misdn_lib_send_event(newbc, EVENT_SETUP);
-
-	/** we should have l3id after sending setup **/
-	ch->l3id = newbc->l3_id;
-
-	if (r == -ENOCHAN) {
-		chan_misdn_log(0, port, " --> * Theres no Channel at the moment .. !\n");
-		chan_misdn_log(1, port, " --> * SEND: State Down pid:%d\n", newbc ? newbc->pid : -1);
-		ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_CIRCUIT_CONGESTION);
-		ast_setstate(ast, AST_STATE_DOWN);
-		return -1;
-	}
-
-	chan_misdn_log(2, port, " --> * SEND: State Dialing pid:%d\n", newbc ? newbc->pid : 1);
-
-	ast_setstate(ast, AST_STATE_DIALING);
-	ast_channel_hangupcause_set(ast, AST_CAUSE_NORMAL_CLEARING);
-
-	if (newbc->nt) {
-		stop_bc_tones(ch);
-	}
-
-	ch->state = MISDN_CALLING;
-
-	return 0;
-}
-
-
-static int misdn_answer(struct ast_channel *ast)
-{
-	struct chan_list *p;
-	const char *tmp;
-
-	if (!ast || !(p = MISDN_ASTERISK_TECH_PVT(ast))) {
-		return -1;
-	}
-
-	chan_misdn_log(1, p ? (p->bc ? p->bc->port : 0) : 0, "* ANSWER:\n");
-
-	if (!p) {
-		ast_log(LOG_WARNING, " --> Channel not connected ??\n");
-		ast_queue_hangup_with_cause(ast, AST_CAUSE_NETWORK_OUT_OF_ORDER);
-	}
-
-	if (!p->bc) {
-		chan_misdn_log(1, 0, " --> Got Answer, but there is no bc obj ??\n");
-
-		ast_queue_hangup_with_cause(ast, AST_CAUSE_PROTOCOL_ERROR);
-	}
-
-	ast_channel_lock(ast);
-	tmp = pbx_builtin_getvar_helper(ast, "CRYPT_KEY");
-	if (!ast_strlen_zero(tmp)) {
-		chan_misdn_log(1, p->bc->port, " --> Connection will be BF crypted\n");
-		ast_copy_string(p->bc->crypt_key, tmp, sizeof(p->bc->crypt_key));
-	} else {
-		chan_misdn_log(3, p->bc->port, " --> Connection is without BF encryption\n");
-	}
-
-	tmp = pbx_builtin_getvar_helper(ast, "MISDN_DIGITAL_TRANS");
-	if (!ast_strlen_zero(tmp) && ast_true(tmp)) {
-		chan_misdn_log(1, p->bc->port, " --> Connection is transparent digital\n");
-		p->bc->nodsp = 1;
-		p->bc->hdlc = 0;
-		p->bc->nojitter = 1;
-	}
-	ast_channel_unlock(ast);
-
-	p->state = MISDN_CONNECTED;
-	stop_indicate(p);
-
-	if (ast_strlen_zero(p->bc->connected.number)) {
-		chan_misdn_log(2,p->bc->port," --> empty connected number using dialed number\n");
-		ast_copy_string(p->bc->connected.number, p->bc->dialed.number, sizeof(p->bc->connected.number));
-
-		/*
-		 * Use the misdn_set_opt() application to set the presentation
-		 * before we answer or you can use the CONECTEDLINE() function
-		 * to set everything before using the Answer() application.
-		 */
-		p->bc->connected.presentation = p->bc->presentation;
-		p->bc->connected.screening = 0;	/* unscreened */
-		p->bc->connected.number_type = p->bc->dialed.number_type;
-		p->bc->connected.number_plan = p->bc->dialed.number_plan;
-	}
-
-	switch (p->bc->outgoing_colp) {
-	case 1:/* restricted */
-	case 2:/* blocked */
-		p->bc->connected.presentation = 1;/* restricted */
-		break;
-	default:
-		break;
-	}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	if (p->bc->div_leg_3_tx_pending) {
-		p->bc->div_leg_3_tx_pending = 0;
-
-		/* Send DivertingLegInformation3 */
-		p->bc->fac_out.Function = Fac_DivertingLegInformation3;
-		p->bc->fac_out.u.DivertingLegInformation3.InvokeID = ++misdn_invoke_id;
-		p->bc->fac_out.u.DivertingLegInformation3.PresentationAllowedIndicator =
-			(p->bc->connected.presentation == 0) ? 1 : 0;
-		print_facility(&p->bc->fac_out, p->bc);
-	}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	misdn_lib_send_event(p->bc, EVENT_CONNECT);
-	start_bc_tones(p);
-
-	return 0;
-}
-
-static int misdn_digit_begin(struct ast_channel *chan, char digit)
-{
-	/* XXX Modify this callback to support Asterisk controlling the length of DTMF */
-	return 0;
-}
-
-static int misdn_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
-{
-	struct chan_list *p;
-	struct misdn_bchannel *bc;
-	char buf[2] = { digit, 0 };
-
-	if (!ast || !(p = MISDN_ASTERISK_TECH_PVT(ast))) {
-		return -1;
-	}
-
-	bc = p->bc;
-	chan_misdn_log(1, bc ? bc->port : 0, "* IND : Digit %c\n", digit);
-
-	if (!bc) {
-		ast_log(LOG_WARNING, " --> !! Got Digit Event without having bchannel Object\n");
-		return -1;
-	}
-
-	switch (p->state) {
-	case MISDN_CALLING:
-		if (strlen(bc->infos_pending) < sizeof(bc->infos_pending) - 1) {
-			strncat(bc->infos_pending, buf, sizeof(bc->infos_pending) - strlen(bc->infos_pending) - 1);
-		}
-		break;
-	case MISDN_CALLING_ACKNOWLEDGE:
-		ast_copy_string(bc->info_dad, buf, sizeof(bc->info_dad));
-		if (strlen(bc->dialed.number) < sizeof(bc->dialed.number) - 1) {
-			strncat(bc->dialed.number, buf, sizeof(bc->dialed.number) - strlen(bc->dialed.number) - 1);
-		}
-		ast_channel_exten_set(p->ast, bc->dialed.number);
-		misdn_lib_send_event(bc, EVENT_INFORMATION);
-		break;
-	default:
-		if (bc->send_dtmf) {
-			send_digit_to_chan(p, digit);
-		}
-		break;
-	}
-
-	return 0;
-}
-
-
-static int misdn_fixup(struct ast_channel *oldast, struct ast_channel *ast)
-{
-	struct chan_list *p;
-
-	if (!ast || !(p = MISDN_ASTERISK_TECH_PVT(ast))) {
-		return -1;
-	}
-
-	chan_misdn_log(1, p->bc ? p->bc->port : 0, "* IND: Got Fixup State:%s L3id:%x\n", misdn_get_ch_state(p), p->l3id);
-
-	p->ast = ast;
-
-	return 0;
-}
-
-
-
-static int misdn_indication(struct ast_channel *ast, int cond, const void *data, size_t datalen)
-{
-	struct chan_list *p;
-
-	if (!ast || !(p = MISDN_ASTERISK_TECH_PVT(ast))) {
-		ast_log(LOG_WARNING, "Returned -1 in misdn_indication\n");
-		return -1;
-	}
-
-	if (!p->bc) {
-		if (p->hold.state == MISDN_HOLD_IDLE) {
-			chan_misdn_log(1, 0, "* IND : Indication [%d] ignored on %s\n", cond,
-				ast_channel_name(ast));
-			ast_log(LOG_WARNING, "Private Pointer but no bc ?\n");
-		} else {
-			chan_misdn_log(1, 0, "* IND : Indication [%d] ignored on hold %s\n",
-				cond, ast_channel_name(ast));
-		}
-		return -1;
-	}
-
-	chan_misdn_log(5, p->bc->port, "* IND : Indication [%d] on %s\n", cond, ast_channel_name(ast));
-
-	switch (cond) {
-	case AST_CONTROL_BUSY:
-		chan_misdn_log(1, p->bc->port, "* IND :\tbusy pid:%d\n", p->bc->pid);
-		ast_setstate(ast, AST_STATE_BUSY);
-
-		p->bc->out_cause = AST_CAUSE_USER_BUSY;
-		if (p->state != MISDN_CONNECTED) {
-			start_bc_tones(p);
-			misdn_lib_send_event(p->bc, EVENT_DISCONNECT);
-		}
-		return -1;
-	case AST_CONTROL_RING:
-		chan_misdn_log(1, p->bc->port, "* IND :\tring pid:%d\n", p->bc->pid);
-		return -1;
-	case AST_CONTROL_RINGING:
-		chan_misdn_log(1, p->bc->port, "* IND :\tringing pid:%d\n", p->bc->pid);
-		switch (p->state) {
-		case MISDN_ALERTING:
-			chan_misdn_log(2, p->bc->port, " --> * IND :\tringing pid:%d but I was Ringing before, so ignoring it\n", p->bc->pid);
-			break;
-		case MISDN_CONNECTED:
-			chan_misdn_log(2, p->bc->port, " --> * IND :\tringing pid:%d but Connected, so just send TONE_ALERTING without state changes \n", p->bc->pid);
-			return -1;
-		default:
-			p->state = MISDN_ALERTING;
-			chan_misdn_log(2, p->bc->port, " --> * IND :\tringing pid:%d\n", p->bc->pid);
-			misdn_lib_send_event(p->bc, EVENT_ALERTING);
-
-			chan_misdn_log(3, p->bc->port, " --> * SEND: State Ring pid:%d\n", p->bc->pid);
-			ast_setstate(ast, AST_STATE_RING);
-
-			if (!p->bc->nt && (p->originator == ORG_MISDN) && !p->incoming_early_audio) {
-				chan_misdn_log(2, p->bc->port, " --> incoming_early_audio off\n");
-			} else {
-				return -1;
-			}
-		}
-		break;
-	case AST_CONTROL_ANSWER:
-		chan_misdn_log(1, p->bc->port, " --> * IND :\tanswer pid:%d\n", p->bc->pid);
-		start_bc_tones(p);
-		break;
-	case AST_CONTROL_TAKEOFFHOOK:
-		chan_misdn_log(1, p->bc->port, " --> *\ttakeoffhook pid:%d\n", p->bc->pid);
-		return -1;
-	case AST_CONTROL_OFFHOOK:
-		chan_misdn_log(1, p->bc->port, " --> *\toffhook pid:%d\n", p->bc->pid);
-		return -1;
-	case AST_CONTROL_FLASH:
-		chan_misdn_log(1, p->bc->port, " --> *\tflash pid:%d\n", p->bc->pid);
-		break;
-	case AST_CONTROL_PROGRESS:
-		chan_misdn_log(1, p->bc->port, " --> * IND :\tprogress pid:%d\n", p->bc->pid);
-		misdn_lib_send_event(p->bc, EVENT_PROGRESS);
-		break;
-	case AST_CONTROL_PROCEEDING:
-		chan_misdn_log(1, p->bc->port, " --> * IND :\tproceeding pid:%d\n", p->bc->pid);
-		misdn_lib_send_event(p->bc, EVENT_PROCEEDING);
-		break;
-	case AST_CONTROL_INCOMPLETE:
-		chan_misdn_log(1, p->bc->port, " --> *\tincomplete pid:%d\n", p->bc->pid);
-		if (!p->overlap_dial) {
-			/* Overlapped dialing not enabled - send hangup */
-			p->bc->out_cause = AST_CAUSE_INVALID_NUMBER_FORMAT;
-			start_bc_tones(p);
-			misdn_lib_send_event(p->bc, EVENT_DISCONNECT);
-
-			if (p->bc->nt) {
-				hanguptone_indicate(p);
-			}
-		}
-		break;
-	case AST_CONTROL_CONGESTION:
-		chan_misdn_log(1, p->bc->port, " --> * IND :\tcongestion pid:%d\n", p->bc->pid);
-
-		p->bc->out_cause = AST_CAUSE_SWITCH_CONGESTION;
-		start_bc_tones(p);
-		misdn_lib_send_event(p->bc, EVENT_DISCONNECT);
-
-		if (p->bc->nt) {
-			hanguptone_indicate(p);
-		}
-		break;
-	case -1 :
-		chan_misdn_log(1, p->bc->port, " --> * IND :\t-1! (stop indication) pid:%d\n", p->bc->pid);
-
-		stop_indicate(p);
-
-		if (p->state == MISDN_CONNECTED) {
-			start_bc_tones(p);
-		}
-		break;
-	case AST_CONTROL_HOLD:
-		ast_moh_start(ast, data, p->mohinterpret);
-		chan_misdn_log(1, p->bc->port, " --> *\tHOLD pid:%d\n", p->bc->pid);
-		break;
-	case AST_CONTROL_UNHOLD:
-		ast_moh_stop(ast);
-		chan_misdn_log(1, p->bc->port, " --> *\tUNHOLD pid:%d\n", p->bc->pid);
-		break;
-	case AST_CONTROL_CONNECTED_LINE:
-		chan_misdn_log(1, p->bc->port, "* IND :\tconnected line update pid:%d\n", p->bc->pid);
-		misdn_update_connected_line(ast, p->bc, p->originator);
-		break;
-	case AST_CONTROL_REDIRECTING:
-		chan_misdn_log(1, p->bc->port, "* IND :\tredirecting info update pid:%d\n", p->bc->pid);
-		misdn_update_redirecting(ast, p->bc, p->originator);
-		break;
-	default:
-		chan_misdn_log(1, p->bc->port, " --> * Unknown Indication:%d pid:%d\n", cond, p->bc->pid);
-		/* fallthrough */
-	case AST_CONTROL_PVT_CAUSE_CODE:
-	case AST_CONTROL_MASQUERADE_NOTIFY:
-		return -1;
-	}
-
-	return 0;
-}
-
-static int misdn_hangup(struct ast_channel *ast)
-{
-	struct chan_list *p;
-	struct misdn_bchannel *bc;
-	const char *var;
-
-	if (!ast) {
-		return -1;
-	}
-
-	ast_debug(1, "misdn_hangup(%s)\n", ast_channel_name(ast));
-
-	/* Take the ast_channel's tech_pvt reference. */
-	ast_mutex_lock(&release_lock);
-	p = MISDN_ASTERISK_TECH_PVT(ast);
-	if (!p) {
-		ast_mutex_unlock(&release_lock);
-		return -1;
-	}
-	MISDN_ASTERISK_TECH_PVT_SET(ast, NULL);
-
-	if (!misdn_chan_is_valid(p)) {
-		ast_mutex_unlock(&release_lock);
-		chan_list_unref(p, "Release ast_channel reference.  Was not active?");
-		return 0;
-	}
-
-	if (p->hold.state == MISDN_HOLD_IDLE) {
-		bc = p->bc;
-	} else {
-		p->hold.state = MISDN_HOLD_DISCONNECT;
-		bc = misdn_lib_find_held_bc(p->hold.port, p->l3id);
-		if (!bc) {
-			chan_misdn_log(4, p->hold.port,
-				"misdn_hangup: Could not find held bc for (%s)\n", ast_channel_name(ast));
-			release_chan_early(p);
-			ast_mutex_unlock(&release_lock);
-			chan_list_unref(p, "Release ast_channel reference");
-			return 0;
-		}
-	}
-
-	if (ast_channel_state(ast) == AST_STATE_RESERVED || p->state == MISDN_NOTHING) {
-		/* between request and call */
-		ast_debug(1, "State Reserved (or nothing) => chanIsAvail\n");
-		release_chan_early(p);
-		if (bc) {
-			misdn_lib_release(bc);
-		}
-		ast_mutex_unlock(&release_lock);
-		chan_list_unref(p, "Release ast_channel reference");
-		return 0;
-	}
-	if (!bc) {
-		ast_log(LOG_WARNING, "Hangup with private but no bc ? state:%s l3id:%x\n",
-			misdn_get_ch_state(p), p->l3id);
-		release_chan_early(p);
-		ast_mutex_unlock(&release_lock);
-		chan_list_unref(p, "Release ast_channel reference");
-		return 0;
-	}
-
-	p->ast = NULL;
-	p->need_hangup = 0;
-	p->need_queue_hangup = 0;
-	p->need_busy = 0;
-
-	if (!bc->nt) {
-		stop_bc_tones(p);
-	}
-
-	bc->out_cause = ast_channel_hangupcause(ast) ? ast_channel_hangupcause(ast) : AST_CAUSE_NORMAL_CLEARING;
-
-	/* Channel lock is already held when we are called. */
-	//ast_channel_lock(ast);
-	var = pbx_builtin_getvar_helper(ast, "HANGUPCAUSE");
-	if (!var) {
-		var = pbx_builtin_getvar_helper(ast, "PRI_CAUSE");
-	}
-	if (var) {
-		int tmpcause;
-
-		tmpcause = atoi(var);
-		bc->out_cause = tmpcause ? tmpcause : AST_CAUSE_NORMAL_CLEARING;
-	}
-
-	var = pbx_builtin_getvar_helper(ast, "MISDN_USERUSER");
-	if (var) {
-		ast_log(LOG_NOTICE, "MISDN_USERUSER: %s\n", var);
-		ast_copy_string(bc->uu, var, sizeof(bc->uu));
-		bc->uulen = strlen(bc->uu);
-	}
-	//ast_channel_unlock(ast);
-
-	chan_misdn_log(1, bc->port,
-		"* IND : HANGUP\tpid:%d context:%s dialed:%s caller:\"%s\" <%s> State:%s\n",
-		bc->pid,
-		ast_channel_context(ast),
-		ast_channel_exten(ast),
-		(ast_channel_caller(ast)->id.name.valid && ast_channel_caller(ast)->id.name.str)
-			? ast_channel_caller(ast)->id.name.str : "",
-		(ast_channel_caller(ast)->id.number.valid && ast_channel_caller(ast)->id.number.str)
-			? ast_channel_caller(ast)->id.number.str : "",
-		misdn_get_ch_state(p));
-	chan_misdn_log(3, bc->port, " --> l3id:%x\n", p->l3id);
-	chan_misdn_log(3, bc->port, " --> cause:%d\n", bc->cause);
-	chan_misdn_log(2, bc->port, " --> out_cause:%d\n", bc->out_cause);
-
-	switch (p->state) {
-	case MISDN_INCOMING_SETUP:
-		/*
-		 * This is the only place in misdn_hangup, where we
-		 * can call release_chan, else it might create a lot of trouble.
-		 */
-		ast_log(LOG_NOTICE, "release channel, in INCOMING_SETUP state.. no other events happened\n");
-		release_chan(p, bc);
-		misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE);
-		ast_mutex_unlock(&release_lock);
-		chan_list_unref(p, "Release ast_channel reference");
-		return 0;
-	case MISDN_DIALING:
-		if (p->hold.state == MISDN_HOLD_IDLE) {
-			start_bc_tones(p);
-			hanguptone_indicate(p);
-		}
-
-		if (bc->need_disconnect) {
-			misdn_lib_send_event(bc, EVENT_DISCONNECT);
-		}
-		break;
-	case MISDN_CALLING_ACKNOWLEDGE:
-		if (p->hold.state == MISDN_HOLD_IDLE) {
-			start_bc_tones(p);
-			hanguptone_indicate(p);
-		}
-
-		if (bc->need_disconnect) {
-			misdn_lib_send_event(bc, EVENT_DISCONNECT);
-		}
-		break;
-
-	case MISDN_CALLING:
-	case MISDN_ALERTING:
-	case MISDN_PROGRESS:
-	case MISDN_PROCEEDING:
-		if (p->originator != ORG_AST && p->hold.state == MISDN_HOLD_IDLE) {
-			hanguptone_indicate(p);
-		}
-
-		if (bc->need_disconnect) {
-			misdn_lib_send_event(bc, EVENT_DISCONNECT);
-		}
-		break;
-	case MISDN_CONNECTED:
-		/*  Alerting or Disconnect */
-		if (bc->nt && p->hold.state == MISDN_HOLD_IDLE) {
-			start_bc_tones(p);
-			hanguptone_indicate(p);
-			bc->progress_indicator = INFO_PI_INBAND_AVAILABLE;
-		}
-		if (bc->need_disconnect) {
-			misdn_lib_send_event(bc, EVENT_DISCONNECT);
-		}
-		break;
-	case MISDN_DISCONNECTED:
-		if (bc->need_release) {
-			misdn_lib_send_event(bc, EVENT_RELEASE);
-		}
-		break;
-
-	case MISDN_CLEANING:
-		ast_mutex_unlock(&release_lock);
-		chan_list_unref(p, "Release ast_channel reference");
-		return 0;
-
-	case MISDN_BUSY:
-		break;
-	default:
-		if (bc->nt) {
-			bc->out_cause = -1;
-			if (bc->need_release) {
-				misdn_lib_send_event(bc, EVENT_RELEASE);
-			}
-		} else {
-			if (bc->need_disconnect) {
-				misdn_lib_send_event(bc, EVENT_DISCONNECT);
-			}
-		}
-		break;
-	}
-
-	p->state = MISDN_CLEANING;
-	chan_misdn_log(3, bc->port, " --> Channel: %s hungup new state:%s\n", ast_channel_name(ast),
-		misdn_get_ch_state(p));
-
-	ast_mutex_unlock(&release_lock);
-	chan_list_unref(p, "Release ast_channel reference");
-	return 0;
-}
-
-
-static struct ast_frame *process_ast_dsp(struct chan_list *tmp, struct ast_frame *frame)
-{
-	struct ast_frame *f;
-
-	if (tmp->dsp) {
-		f = ast_dsp_process(tmp->ast, tmp->dsp, frame);
-	} else {
-		chan_misdn_log(0, tmp->bc->port, "No DSP-Path found\n");
-		return NULL;
-	}
-
-	if (!f || (f->frametype != AST_FRAME_DTMF)) {
-		return f;
-	}
-
-	ast_debug(1, "Detected inband DTMF digit: %c\n", f->subclass.integer);
-
-	if (tmp->faxdetect && (f->subclass.integer == 'f')) {
-		/* Fax tone -- Handle and return NULL */
-		if (!tmp->faxhandled) {
-			struct ast_channel *ast = tmp->ast;
-			tmp->faxhandled++;
-			chan_misdn_log(0, tmp->bc->port, "Fax detected, preparing %s for fax transfer.\n", ast_channel_name(ast));
-			tmp->bc->rxgain = 0;
-			isdn_lib_update_rxgain(tmp->bc);
-			tmp->bc->txgain = 0;
-			isdn_lib_update_txgain(tmp->bc);
-#ifdef MISDN_1_2
-			*tmp->bc->pipeline = 0;
-#else
-			tmp->bc->ec_enable = 0;
-#endif
-			isdn_lib_update_ec(tmp->bc);
-			isdn_lib_stop_dtmf(tmp->bc);
-			switch (tmp->faxdetect) {
-			case 1:
-				if (strcmp(ast_channel_exten(ast), "fax")) {
-					const char *context;
-					char context_tmp[BUFFERSIZE];
-					misdn_cfg_get(tmp->bc->port, MISDN_CFG_FAXDETECT_CONTEXT, &context_tmp, sizeof(context_tmp));
-					context = S_OR(context_tmp, S_OR(ast_channel_macrocontext(ast), ast_channel_context(ast)));
-					if (ast_exists_extension(ast, context, "fax", 1,
-						S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, NULL))) {
-						ast_verb(3, "Redirecting %s to fax extension (context:%s)\n", ast_channel_name(ast), context);
-						/* Save the DID/DNIS when we transfer the fax call to a "fax" extension */
-						pbx_builtin_setvar_helper(ast,"FAXEXTEN",ast_channel_exten(ast));
-						if (ast_async_goto(ast, context, "fax", 1)) {
-							ast_log(LOG_WARNING, "Failed to async goto '%s' into fax of '%s'\n", ast_channel_name(ast), context);
-						}
-					} else {
-						ast_log(LOG_NOTICE, "Fax detected but no fax extension, context:%s exten:%s\n", context, ast_channel_exten(ast));
-					}
-				} else {
-					ast_debug(1, "Already in a fax extension, not redirecting\n");
-				}
-				break;
-			case 2:
-				ast_verb(3, "Not redirecting %s to fax extension, nojump is set.\n", ast_channel_name(ast));
-				break;
-			default:
-				break;
-			}
-		} else {
-			ast_debug(1, "Fax already handled\n");
-		}
-	}
-
-	if (tmp->ast_dsp && (f->subclass.integer != 'f')) {
-		chan_misdn_log(2, tmp->bc->port, " --> * SEND: DTMF (AST_DSP) :%c\n", f->subclass.integer);
-	}
-
-	return f;
-}
-
-
-static struct ast_frame *misdn_read(struct ast_channel *ast)
-{
-	struct chan_list *tmp;
-	int len, t;
-	struct pollfd pfd = { .fd = -1, .events = POLLIN };
-
-	if (!ast) {
-		chan_misdn_log(1, 0, "misdn_read called without ast\n");
-		return NULL;
-	}
-	if (!(tmp = MISDN_ASTERISK_TECH_PVT(ast))) {
-		chan_misdn_log(1, 0, "misdn_read called without ast->pvt\n");
-		return NULL;
-	}
-
-	if (!tmp->bc && tmp->hold.state == MISDN_HOLD_IDLE) {
-		chan_misdn_log(1, 0, "misdn_read called without bc\n");
-		return NULL;
-	}
-
-	pfd.fd = tmp->pipe[0];
-	t = ast_poll(&pfd, 1, 20);
-
-	if (t < 0) {
-		chan_misdn_log(-1, tmp->bc->port, "poll() error (err=%s)\n", strerror(errno));
-		return NULL;
-	}
-
-	if (!t) {
-		chan_misdn_log(3, tmp->bc->port, "poll() timed out\n");
-		len = 160;
-	} else if (pfd.revents & POLLIN) {
-		len = read(tmp->pipe[0], tmp->ast_rd_buf, sizeof(tmp->ast_rd_buf));
-
-		if (len <= 0) {
-			/* we hangup here, since our pipe is closed */
-			chan_misdn_log(2, tmp->bc->port, "misdn_read: Pipe closed, hanging up\n");
-			return NULL;
-		}
-	} else {
-		return NULL;
-	}
-
-	tmp->frame.frametype = AST_FRAME_VOICE;
-	tmp->frame.subclass.format = ast_format_alaw;
-	tmp->frame.datalen = len;
-	tmp->frame.samples = len;
-	tmp->frame.mallocd = 0;
-	tmp->frame.offset = 0;
-	tmp->frame.delivery = ast_tv(0, 0);
-	tmp->frame.src = NULL;
-	tmp->frame.data.ptr = tmp->ast_rd_buf;
-
-	if (tmp->faxdetect && !tmp->faxhandled) {
-		if (tmp->faxdetect_timeout) {
-			if (ast_tvzero(tmp->faxdetect_tv)) {
-				tmp->faxdetect_tv = ast_tvnow();
-				chan_misdn_log(2, tmp->bc->port, "faxdetect: starting detection with timeout: %ds ...\n", tmp->faxdetect_timeout);
-				return process_ast_dsp(tmp, &tmp->frame);
-			} else {
-				struct timeval tv_now = ast_tvnow();
-				int diff = ast_tvdiff_ms(tv_now, tmp->faxdetect_tv);
-				if (diff <= (tmp->faxdetect_timeout * 1000)) {
-					chan_misdn_log(5, tmp->bc->port, "faxdetect: detecting ...\n");
-					return process_ast_dsp(tmp, &tmp->frame);
-				} else {
-					chan_misdn_log(2, tmp->bc->port, "faxdetect: stopping detection (time ran out) ...\n");
-					tmp->faxdetect = 0;
-					return &tmp->frame;
-				}
-			}
-		} else {
-			chan_misdn_log(5, tmp->bc->port, "faxdetect: detecting ... (no timeout)\n");
-			return process_ast_dsp(tmp, &tmp->frame);
-		}
-	} else {
-		if (tmp->ast_dsp) {
-			return process_ast_dsp(tmp, &tmp->frame);
-		} else {
-			return &tmp->frame;
-		}
-	}
-}
-
-
-static int misdn_write(struct ast_channel *ast, struct ast_frame *frame)
-{
-	struct chan_list *ch;
-
-	if (!ast || !(ch = MISDN_ASTERISK_TECH_PVT(ast))) {
-		return -1;
-	}
-
-	if (ch->hold.state != MISDN_HOLD_IDLE) {
-		chan_misdn_log(7, 0, "misdn_write: Returning because hold active\n");
-		return 0;
-	}
-
-	if (!ch->bc) {
-		ast_log(LOG_WARNING, "private but no bc\n");
-		return -1;
-	}
-
-	if (ch->notxtone) {
-		chan_misdn_log(7, ch->bc->port, "misdn_write: Returning because notxtone\n");
-		return 0;
-	}
-
-
-	if (!frame->subclass.format) {
-		chan_misdn_log(4, ch->bc->port, "misdn_write: * prods us\n");
-		return 0;
-	}
-
-	if (ast_format_cmp(frame->subclass.format, ast_format_alaw) == AST_FORMAT_CMP_NOT_EQUAL) {
-		chan_misdn_log(-1, ch->bc->port, "Got Unsupported Frame with Format:%s\n",
-			ast_format_get_name(frame->subclass.format));
-		return 0;
-	}
-
-
-	if (!frame->samples) {
-		chan_misdn_log(4, ch->bc->port, "misdn_write: zero write\n");
-
-		if (!strcmp(frame->src,"ast_prod")) {
-			chan_misdn_log(1, ch->bc->port, "misdn_write: state (%s) prodded.\n", misdn_get_ch_state(ch));
-
-			if (ch->ts) {
-				chan_misdn_log(4, ch->bc->port, "Starting Playtones\n");
-				misdn_lib_tone_generator_start(ch->bc);
-			}
-			return 0;
-		}
-
-		return -1;
-	}
-
-	if (!ch->bc->addr) {
-		chan_misdn_log(8, ch->bc->port, "misdn_write: no addr for bc dropping:%d\n", frame->samples);
-		return 0;
-	}
-
-#ifdef MISDN_DEBUG
-	{
-		int i;
-		int max = 5 > frame->samples ? frame->samples : 5;
-
-		ast_debug(1, "write2mISDN %p %d bytes: ", p, frame->samples);
-
-		for (i = 0; i < max; i++) {
-			ast_debug(1, "%02hhx ", ((unsigned char *) frame->data.ptr)[i]);
-		}
-	}
-#endif
-
-	switch (ch->bc->bc_state) {
-	case BCHAN_ACTIVATED:
-	case BCHAN_BRIDGED:
-		break;
-	default:
-		if (!ch->dropped_frame_cnt) {
-			chan_misdn_log(5, ch->bc->port,
-				"BC not active (nor bridged) dropping: %d frames addr:%x exten:%s cid:%s ch->state:%s bc_state:%d l3id:%x\n",
-				frame->samples, ch->bc->addr, ast_channel_exten(ast),
-				S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, ""),
-				misdn_get_ch_state(ch), ch->bc->bc_state, ch->bc->l3_id);
-		}
-
-		if (++ch->dropped_frame_cnt > 100) {
-			ch->dropped_frame_cnt = 0;
-			chan_misdn_log(5, ch->bc->port, "BC not active (nor bridged) dropping: %d frames addr:%x  dropped > 100 frames!\n", frame->samples, ch->bc->addr);
-		}
-
-		return 0;
-	}
-
-	chan_misdn_log(9, ch->bc->port, "Sending :%d bytes to MISDN\n", frame->samples);
-	if (!ch->bc->nojitter && misdn_cap_is_speech(ch->bc->capability)) {
-		/* Buffered Transmit (triggered by read from isdn side)*/
-		if (misdn_jb_fill(ch->jb, frame->data.ptr, frame->samples) < 0) {
-			if (ch->bc->active) {
-				cb_log(0, ch->bc->port, "Misdn Jitterbuffer Overflow.\n");
-			}
-		}
-
-	} else {
-		/* transmit without jitterbuffer */
-		misdn_lib_tx2misdn_frm(ch->bc, frame->data.ptr, frame->samples);
-	}
-
-	return 0;
-}
-
-#if defined(mISDN_NATIVE_BRIDGING)
-static enum ast_bridge_result misdn_bridge(struct ast_channel *c0,
-	struct ast_channel *c1, int flags,
-	struct ast_frame **fo,
-	struct ast_channel **rc,
-	int timeoutms)
-{
-	struct chan_list *ch1, *ch2;
-	struct ast_channel *carr[2], *who;
-	int to = -1;
-	struct ast_frame *f;
-	int p1_b, p2_b;
-	int bridging;
-
-	misdn_cfg_get(0, MISDN_GEN_BRIDGING, &bridging, sizeof(bridging));
-	if (!bridging) {
-		/* Native mISDN bridging globally disabled. */
-		return AST_BRIDGE_FAILED_NOWARN;
-	}
-
-	ch1 = get_chan_by_ast(c0);
-	if (!ch1) {
-		return AST_BRIDGE_FAILED;
-	}
-	ch2 = get_chan_by_ast(c1);
-	if (!ch2) {
-		chan_list_unref(ch1, "Failed to find ch2");
-		return AST_BRIDGE_FAILED;
-	}
-
-	carr[0] = c0;
-	carr[1] = c1;
-
-	misdn_cfg_get(ch1->bc->port, MISDN_CFG_BRIDGING, &p1_b, sizeof(p1_b));
-	misdn_cfg_get(ch2->bc->port, MISDN_CFG_BRIDGING, &p2_b, sizeof(p2_b));
-	if (!p1_b || !p2_b) {
-		ast_log(LOG_NOTICE, "Falling back to Asterisk bridging\n");
-		chan_list_unref(ch1, "Bridge fallback ch1");
-		chan_list_unref(ch2, "Bridge fallback ch2");
-		return AST_BRIDGE_FAILED_NOWARN;
-	}
-
-	/* make a mISDN_dsp conference */
-	chan_misdn_log(1, ch1->bc->port, "I SEND: Making conference with Number:%d\n", ch1->bc->pid + 1);
-	misdn_lib_bridge(ch1->bc, ch2->bc);
-
-	ast_verb(3, "Native bridging %s and %s\n", ast_channel_name(c0), ast_channel_name(c1));
-
-	chan_misdn_log(1, ch1->bc->port, "* Making Native Bridge between \"%s\" <%s> and \"%s\" <%s>\n",
-		ch1->bc->caller.name,
-		ch1->bc->caller.number,
-		ch2->bc->caller.name,
-		ch2->bc->caller.number);
-
-	if (!(flags & AST_BRIDGE_DTMF_CHANNEL_0)) {
-		ch1->ignore_dtmf = 1;
-	}
-
-	if (!(flags & AST_BRIDGE_DTMF_CHANNEL_1)) {
-		ch2->ignore_dtmf = 1;
-	}
-
-	for (;/*ever*/;) {
-		to = -1;
-		who = ast_waitfor_n(carr, 2, &to);
-
-		if (!who) {
-			ast_log(LOG_NOTICE, "misdn_bridge: empty read, breaking out\n");
-			break;
-		}
-		f = ast_read(who);
-
-		if (!f || (f->frametype == AST_FRAME_CONTROL && f->subclass.integer != AST_CONTROL_PVT_CAUSE_CODE)) {
-			/* got hangup .. */
-
-			if (!f) {
-				chan_misdn_log(4, ch1->bc->port, "Read Null Frame\n");
-			} else {
-				chan_misdn_log(4, ch1->bc->port, "Read Frame Control class:%d\n", f->subclass.integer);
-			}
-
-			*fo = f;
-			*rc = who;
-			break;
-		}
-
-		if (f->frametype == AST_FRAME_DTMF) {
-			chan_misdn_log(1, 0, "Read DTMF %d from %s\n", f->subclass.integer, ast_channel_exten(who));
-
-			*fo = f;
-			*rc = who;
-			break;
-		}
-
-#if 0
-		if (f->frametype == AST_FRAME_VOICE) {
-			chan_misdn_log(1, ch1->bc->port, "I SEND: Splitting conference with Number:%d\n", ch1->bc->pid +1);
-
-			continue;
-		}
-#endif
-
-		if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_PVT_CAUSE_CODE) {
-			ast_channel_hangupcause_hash_set((who == c0) ? c1 : c0, f->data.ptr, f->datalen);
-		} else {
-			ast_write((who == c0) ? c1 : c0, f);
-		}
-	}
-
-	chan_misdn_log(1, ch1->bc->port, "I SEND: Splitting conference with Number:%d\n", ch1->bc->pid + 1);
-
-	misdn_lib_split_bridge(ch1->bc, ch2->bc);
-
-	chan_list_unref(ch1, "Bridge complete ch1");
-	chan_list_unref(ch2, "Bridge complete ch2");
-	return AST_BRIDGE_COMPLETE;
-}
-#endif	/* defined(mISDN_NATIVE_BRIDGING) */
-
-/** AST INDICATIONS END **/
-
-static int dialtone_indicate(struct chan_list *cl)
-{
-	struct ast_channel *ast = cl->ast;
-	int nd = 0;
-
-	if (!ast) {
-		chan_misdn_log(0, cl->bc->port, "No Ast in dialtone_indicate\n");
-		return -1;
-	}
-
-	misdn_cfg_get(cl->bc->port, MISDN_CFG_NODIALTONE, &nd, sizeof(nd));
-
-	if (nd) {
-		chan_misdn_log(1, cl->bc->port, "Not sending Dialtone, because config wants it\n");
-		return 0;
-	}
-
-	chan_misdn_log(3, cl->bc->port, " --> Dial\n");
-
-	cl->ts = ast_get_indication_tone(ast_channel_zone(ast), "dial");
-
-	if (cl->ts) {
-		cl->notxtone = 0;
-		cl->norxtone = 0;
-		/* This prods us in misdn_write */
-		ast_playtones_start(ast, 0, cl->ts->data, 0);
-	}
-
-	return 0;
-}
-
-static void hanguptone_indicate(struct chan_list *cl)
-{
-	misdn_lib_send_tone(cl->bc, TONE_HANGUP);
-}
-
-static int stop_indicate(struct chan_list *cl)
-{
-	struct ast_channel *ast = cl->ast;
-
-	if (!ast) {
-		chan_misdn_log(0, cl->bc->port, "No Ast in stop_indicate\n");
-		return -1;
-	}
-
-	chan_misdn_log(3, cl->bc->port, " --> None\n");
-	misdn_lib_tone_generator_stop(cl->bc);
-	ast_playtones_stop(ast);
-
-	if (cl->ts) {
-		cl->ts = ast_tone_zone_sound_unref(cl->ts);
-	}
-
-	return 0;
-}
-
-
-static int start_bc_tones(struct chan_list* cl)
-{
-	misdn_lib_tone_generator_stop(cl->bc);
-	cl->notxtone = 0;
-	cl->norxtone = 0;
-	return 0;
-}
-
-static int stop_bc_tones(struct chan_list *cl)
-{
-	if (!cl) {
-		return -1;
-	}
-
-	cl->notxtone = 1;
-	cl->norxtone = 1;
-
-	return 0;
-}
-
-/*!
- * \internal
- * \brief Destroy the chan_list object.
- *
- * \param obj chan_list object to destroy.
- */
-static void chan_list_destructor(void *obj)
-{
-	struct chan_list *ch = obj;
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	if (ch->peer) {
-		ao2_ref(ch->peer, -1);
-		ch->peer = NULL;
-	}
-#endif /* AST_MISDN_ENHANCEMENTS */
-
-	if (ch->dsp) {
-		ast_dsp_free(ch->dsp);
-		ch->dsp = NULL;
-	}
-
-	/* releasing jitterbuffer */
-	if (ch->jb) {
-		misdn_jb_destroy(ch->jb);
-		ch->jb = NULL;
-	}
-
-	if (ch->overlap_dial) {
-		if (ch->overlap_dial_task != -1) {
-			misdn_tasks_remove(ch->overlap_dial_task);
-			ch->overlap_dial_task = -1;
-		}
-		ast_mutex_destroy(&ch->overlap_tv_lock);
-	}
-
-	if (-1 < ch->pipe[0]) {
-		close(ch->pipe[0]);
-	}
-	if (-1 < ch->pipe[1]) {
-		close(ch->pipe[1]);
-	}
-}
-
-/*! Returns a reference to the new chan_list. */
-static struct chan_list *chan_list_init(int orig)
-{
-	struct chan_list *cl;
-
-	cl = ao2_alloc(sizeof(*cl), chan_list_destructor);
-	if (!cl) {
-		chan_misdn_log(-1, 0, "misdn_request: malloc failed!");
-		return NULL;
-	}
-
-	cl->originator = orig;
-	cl->need_queue_hangup = 1;
-	cl->need_hangup = 1;
-	cl->need_busy = 1;
-	cl->overlap_dial_task = -1;
-#if defined(AST_MISDN_ENHANCEMENTS)
-	cl->record_id = -1;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	cl->pipe[0] = -1;
-	cl->pipe[1] = -1;
-
-	return cl;
-}
-
-static struct ast_channel *misdn_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
-{
-	struct ast_channel *ast;
-	char group[BUFFERSIZE + 1] = "";
-	char dial_str[128];
-	char *dest_cp;
-	char *p = NULL;
-	int channel = 0;
-	int port = 0;
-	struct misdn_bchannel *newbc = NULL;
-	int dec = 0;
-#if defined(AST_MISDN_ENHANCEMENTS)
-	int cc_retry_call = 0;	/* TRUE if this is a call completion retry call */
-	long record_id = -1;
-	struct misdn_cc_record *cc_record;
-	const char *err_msg;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	struct chan_list *cl;
-
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(intf);	/* interface token */
-		AST_APP_ARG(ext);	/* extension token */
-		AST_APP_ARG(opts);	/* options token */
-	);
-
-	snprintf(dial_str, sizeof(dial_str), "%s/%s", misdn_type, data);
-
-	/*
-	 * data is ---v
-	 * Dial(mISDN/g:group_name[/extension[/options]])
-	 * Dial(mISDN/port[:preselected_channel][/extension[/options]])
-	 * Dial(mISDN/cc/cc-record-id)
-	 *
-	 * The dial extension could be empty if you are using MISDN_KEYPAD
-	 * to control ISDN provider features.
-	 */
-	dest_cp = ast_strdupa(data);
-	AST_NONSTANDARD_APP_ARGS(args, dest_cp, '/');
-	if (!args.ext) {
-		args.ext = "";
-	}
-
-	if (!ast_strlen_zero(args.intf)) {
-		if (args.intf[0] == 'g' && args.intf[1] == ':') {
-			/* We make a group call lets checkout which ports are in my group */
-			args.intf += 2;
-			ast_copy_string(group, args.intf, sizeof(group));
-			chan_misdn_log(2, 0, " --> Group Call group: %s\n", group);
-#if defined(AST_MISDN_ENHANCEMENTS)
-		} else if (strcmp(args.intf, "cc") == 0) {
-			cc_retry_call = 1;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-		} else if ((p = strchr(args.intf, ':'))) {
-			/* we have a preselected channel */
-			*p++ = 0;
-			channel = atoi(p);
-			port = atoi(args.intf);
-			chan_misdn_log(2, port, " --> Call on preselected Channel (%d).\n", channel);
-		} else {
-			port = atoi(args.intf);
-		}
-	} else {
-		ast_log(LOG_WARNING, " --> ! IND : Dial(%s) WITHOUT Port or Group, check extensions.conf\n", dial_str);
-		return NULL;
-	}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	if (cc_retry_call) {
-		if (ast_strlen_zero(args.ext)) {
-			ast_log(LOG_WARNING, " --> ! IND : Dial(%s) WITHOUT cc-record-id, check extensions.conf\n", dial_str);
-			return NULL;
-		}
-		if (!isdigit(*args.ext)) {
-			ast_log(LOG_WARNING, " --> ! IND : Dial(%s) cc-record-id must be a number.\n", dial_str);
-			return NULL;
-		}
-		record_id = atol(args.ext);
-
-		AST_LIST_LOCK(&misdn_cc_records_db);
-		cc_record = misdn_cc_find_by_id(record_id);
-		if (!cc_record) {
-			AST_LIST_UNLOCK(&misdn_cc_records_db);
-			err_msg = misdn_cc_record_not_found;
-			ast_log(LOG_WARNING, " --> ! IND : Dial(%s) %s.\n", dial_str, err_msg);
-			return NULL;
-		}
-		if (!cc_record->activated) {
-			AST_LIST_UNLOCK(&misdn_cc_records_db);
-			err_msg = "Call completion has not been activated";
-			ast_log(LOG_WARNING, " --> ! IND : Dial(%s) %s.\n", dial_str, err_msg);
-			return NULL;
-		}
-		port = cc_record->port;
-		AST_LIST_UNLOCK(&misdn_cc_records_db);
-	}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-	if (misdn_cfg_is_group_method(group, METHOD_STANDARD_DEC)) {
-		chan_misdn_log(4, port, " --> STARTING STANDARD DEC...\n");
-		dec = 1;
-	}
-
-	if (!ast_strlen_zero(group)) {
-		char cfg_group[BUFFERSIZE + 1];
-		struct robin_list *rr = NULL;
-
-		/* Group dial */
-
-		if (misdn_cfg_is_group_method(group, METHOD_ROUND_ROBIN)) {
-			chan_misdn_log(4, port, " --> STARTING ROUND ROBIN...\n");
-			rr = get_robin_position(group);
-		}
-
-		if (rr) {
-			int port_start;
-			int bchan_start;
-			int port_up;
-			int check;
-			int maxbchans;
-			int wraped = 0;
-
-			if (!rr->port) {
-				rr->port = misdn_cfg_get_next_port_spin(0);
-			}
-
-			if (!rr->channel) {
-				rr->channel = 1;
-			}
-
-			bchan_start = rr->channel;
-			port_start = rr->port;
-			do {
-				misdn_cfg_get(rr->port, MISDN_CFG_GROUPNAME, cfg_group, sizeof(cfg_group));
-				if (strcasecmp(cfg_group, group)) {
-					wraped = 1;
-					rr->port = misdn_cfg_get_next_port_spin(rr->port);
-					rr->channel = 1;
-					continue;
-				}
-
-				misdn_cfg_get(rr->port, MISDN_CFG_PMP_L1_CHECK, &check, sizeof(check));
-				port_up = misdn_lib_port_up(rr->port, check);
-
-				if (!port_up) {
-					chan_misdn_log(1, rr->port, "L1 is not Up on this Port\n");
-					rr->port = misdn_cfg_get_next_port_spin(rr->port);
-					rr->channel = 1;
-				} else if (port_up < 0) {
-					ast_log(LOG_WARNING, "This port (%d) is blocked\n", rr->port);
-					rr->port = misdn_cfg_get_next_port_spin(rr->port);
-					rr->channel = 1;
-				} else {
-					chan_misdn_log(4, rr->port, "portup\n");
-					maxbchans = misdn_lib_get_maxchans(rr->port);
-
-					for (;rr->channel <= maxbchans;rr->channel++) {
-						/* ive come full circle and can stop now */
-						if (wraped && (rr->port == port_start) && (rr->channel == bchan_start)) {
-							break;
-						}
-
-						chan_misdn_log(4, rr->port, "Checking channel %d\n",  rr->channel);
-
-						if ((newbc = misdn_lib_get_free_bc(rr->port, rr->channel, 0, 0))) {
-							chan_misdn_log(4, rr->port, " Success! Found port:%d channel:%d\n", newbc->port, newbc->channel);
-							rr->channel++;
-							break;
-						}
-					}
-					if (wraped && (rr->port == port_start) && (rr->channel <= bchan_start)) {
-						break;
-					} else if (!newbc || (rr->channel == maxbchans)) {
-						rr->port = misdn_cfg_get_next_port_spin(rr->port);
-						rr->channel = 1;
-					}
-
-				}
-				wraped = 1;
-			} while (!newbc && (rr->port > 0));
-		} else {
-			for (port = misdn_cfg_get_next_port(0); port > 0;
-				port = misdn_cfg_get_next_port(port)) {
-				misdn_cfg_get(port, MISDN_CFG_GROUPNAME, cfg_group, sizeof(cfg_group));
-
-				chan_misdn_log(3, port, "Group [%s] Port [%d]\n", group, port);
-				if (!strcasecmp(cfg_group, group)) {
-					int port_up;
-					int check;
-
-					misdn_cfg_get(port, MISDN_CFG_PMP_L1_CHECK, &check, sizeof(check));
-					port_up = misdn_lib_port_up(port, check);
-
-					chan_misdn_log(4, port, "portup:%d\n", port_up);
-
-					if (port_up > 0) {
-						newbc = misdn_lib_get_free_bc(port, 0, 0, dec);
-						if (newbc) {
-							break;
-						}
-					}
-				}
-			}
-		}
-
-		/* Group dial failed ?*/
-		if (!newbc) {
-			ast_log(LOG_WARNING,
-				"Could not Dial out on group '%s'.\n"
-				"\tEither the L2 and L1 on all of these ports where DOWN (see 'show application misdn_check_l2l1')\n"
-				"\tOr there was no free channel on none of the ports\n\n",
-				group);
-			return NULL;
-		}
-	} else {
-		/* 'Normal' Port dial * Port dial */
-		if (channel) {
-			chan_misdn_log(1, port, " --> preselected_channel: %d\n", channel);
-		}
-		newbc = misdn_lib_get_free_bc(port, channel, 0, dec);
-		if (!newbc) {
-			ast_log(LOG_WARNING, "Could not create channel on port:%d for Dial(%s)\n", port, dial_str);
-			return NULL;
-		}
-	}
-
-	/* create ast_channel and link all the objects together */
-	cl = chan_list_init(ORG_AST);
-	if (!cl) {
-		misdn_lib_release(newbc);
-		ast_log(LOG_ERROR, "Could not create call record for Dial(%s)\n", dial_str);
-		return NULL;
-	}
-	cl->bc = newbc;
-
-	ast = misdn_new(cl, AST_STATE_RESERVED, args.ext, NULL, cap, assignedids, requestor, port, channel);
-	if (!ast) {
-		chan_list_unref(cl, "Failed to create a new channel");
-		misdn_lib_release(newbc);
-		ast_log(LOG_ERROR, "Could not create Asterisk channel for Dial(%s)\n", dial_str);
-		return NULL;
-	}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	cl->record_id = record_id;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-	/* register chan in local list */
-	cl_queue_chan(cl);
-
-	/* fill in the config into the objects */
-	read_config(cl);
-
-	/* important */
-	cl->need_hangup = 0;
-
-	chan_list_unref(cl, "Successful misdn_request()");
-	return ast;
-}
-
-
-static int misdn_send_text(struct ast_channel *chan, const char *text)
-{
-	struct chan_list *tmp = MISDN_ASTERISK_TECH_PVT(chan);
-
-	if (tmp && tmp->bc) {
-		ast_copy_string(tmp->bc->display, text, sizeof(tmp->bc->display));
-		misdn_lib_send_event(tmp->bc, EVENT_INFORMATION);
-	} else {
-		ast_log(LOG_WARNING, "No chan_list but send_text request?\n");
-		return -1;
-	}
-
-	return 0;
-}
-
-static struct ast_channel_tech misdn_tech = {
-	.type = misdn_type,
-	.description = "Channel driver for mISDN Support (Bri/Pri)",
-	.requester = misdn_request,
-	.send_digit_begin = misdn_digit_begin,
-	.send_digit_end = misdn_digit_end,
-	.call = misdn_call,
-	.hangup = misdn_hangup,
-	.answer = misdn_answer,
-	.read = misdn_read,
-	.write = misdn_write,
-	.indicate = misdn_indication,
-	.fixup = misdn_fixup,
-	.send_text = misdn_send_text,
-	.properties = 0,
-};
-
-
-static int glob_channel = 0;
-
-static void update_name(struct ast_channel *tmp, int port, int c)
-{
-	int chan_offset = 0;
-	int tmp_port = misdn_cfg_get_next_port(0);
-	char newname[255];
-
-	for (; tmp_port > 0; tmp_port = misdn_cfg_get_next_port(tmp_port)) {
-		if (tmp_port == port) {
-			break;
-		}
-		chan_offset += misdn_lib_port_is_pri(tmp_port) ? 30 : 2;
-	}
-	if (c < 0) {
-		c = 0;
-	}
-
-	snprintf(newname, sizeof(newname), "%s/%d-", misdn_type, chan_offset + c);
-	if (strncmp(ast_channel_name(tmp), newname, strlen(newname))) {
-		snprintf(newname, sizeof(newname), "%s/%d-u%d", misdn_type, chan_offset + c, glob_channel++);
-		ast_change_name(tmp, newname);
-		chan_misdn_log(3, port, " --> updating channel name to [%s]\n", ast_channel_name(tmp));
-	}
-}
-
-static struct ast_channel *misdn_new(struct chan_list *chlist, int state,  char *exten, char *callerid, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, int port, int c)
-{
-	struct ast_format_cap *native;
-	struct ast_channel *tmp;
-	char *cid_name = NULL;
-	char *cid_num = NULL;
-	int chan_offset = 0;
-	int tmp_port = misdn_cfg_get_next_port(0);
-	struct ast_format *tmpfmt;
-
-	native = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
-	if (!native) {
-		return NULL;
-	}
-
-	for (; tmp_port > 0; tmp_port = misdn_cfg_get_next_port(tmp_port)) {
-		if (tmp_port == port) {
-			break;
-		}
-		chan_offset += misdn_lib_port_is_pri(tmp_port) ? 30 : 2;
-	}
-	if (c < 0) {
-		c = 0;
-	}
-
-	if (callerid) {
-		ast_callerid_parse(callerid, &cid_name, &cid_num);
-	}
-
-	tmp = ast_channel_alloc(1, state, cid_num, cid_name, "", exten, "", assignedids, requestor, 0, "%s/%s%d-u%d", misdn_type, c ? "" : "tmp", chan_offset + c, glob_channel++);
-	if (tmp) {
-		chan_misdn_log(2, port, " --> * NEW CHANNEL dialed:%s caller:%s\n", exten, callerid);
-
-		tmpfmt = ast_format_cap_get_format(cap, 0);
-		ast_format_cap_append(native, ast_format_alaw, 0);
-		ast_channel_nativeformats_set(tmp, native);
-		ast_channel_set_writeformat(tmp, tmpfmt);
-		ast_channel_set_rawwriteformat(tmp, tmpfmt);
-		ast_channel_set_readformat(tmp, tmpfmt);
-		ast_channel_set_rawreadformat(tmp, tmpfmt);
-
-		ao2_ref(tmpfmt, -1);
-
-		/* Link the channel and private together */
-		chan_list_ref(chlist, "Give a reference to ast_channel");
-		MISDN_ASTERISK_TECH_PVT_SET(tmp, chlist);
-		chlist->ast = tmp;
-
-		ast_channel_tech_set(tmp, &misdn_tech);
-
-		ast_channel_priority_set(tmp, 1);
-
-		if (exten) {
-			ast_channel_exten_set(tmp, exten);
-		} else {
-			chan_misdn_log(1, 0, "misdn_new: no exten given.\n");
-		}
-
-		if (!ast_strlen_zero(cid_num)) {
-			/* Don't use ast_set_callerid() here because it will
-			 * generate a needless NewCallerID event */
-			ast_channel_caller(tmp)->ani.number.valid = 1;
-			ast_channel_caller(tmp)->ani.number.str = ast_strdup(cid_num);
-		}
-
-		if (pipe(chlist->pipe) < 0) {
-			ast_log(LOG_ERROR, "Pipe failed\n");
-		}
-		ast_channel_set_fd(tmp, 0, chlist->pipe[0]);
-
-		ast_channel_rings_set(tmp, (state == AST_STATE_RING) ? 1 : 0);
-
-		ast_jb_configure(tmp, misdn_get_global_jbconf());
-
-		ast_channel_unlock(tmp);
-	} else {
-		chan_misdn_log(-1, 0, "Unable to allocate channel structure\n");
-	}
-
-	ao2_ref(native, -1);
-
-	return tmp;
-}
-
-/*! Returns a reference to the found chan_list. */
-static struct chan_list *find_chan_by_bc(struct misdn_bchannel *bc)
-{
-	struct chan_list *help;
-
-	ast_mutex_lock(&cl_te_lock);
-	for (help = cl_te; help; help = help->next) {
-		if (help->bc == bc) {
-			chan_list_ref(help, "Found chan_list by bc");
-			ast_mutex_unlock(&cl_te_lock);
-			return help;
-		}
-	}
-	ast_mutex_unlock(&cl_te_lock);
-
-	chan_misdn_log(6, bc->port,
-		"$$$ find_chan_by_bc: No channel found for dialed:%s caller:\"%s\" <%s>\n",
-		bc->dialed.number,
-		bc->caller.name,
-		bc->caller.number);
-
-	return NULL;
-}
-
-/*! Returns a reference to the found chan_list. */
-static struct chan_list *find_hold_call(struct misdn_bchannel *bc)
-{
-	struct chan_list *help;
-
-	if (bc->pri) {
-		return NULL;
-	}
-
-	chan_misdn_log(6, bc->port, "$$$ find_hold_call: channel:%d dialed:%s caller:\"%s\" <%s>\n",
-		bc->channel,
-		bc->dialed.number,
-		bc->caller.name,
-		bc->caller.number);
-	ast_mutex_lock(&cl_te_lock);
-	for (help = cl_te; help; help = help->next) {
-		chan_misdn_log(4, bc->port, "$$$ find_hold_call: --> hold:%d channel:%d\n", help->hold.state, help->hold.channel);
-		if (help->hold.state == MISDN_HOLD_ACTIVE && help->hold.port == bc->port) {
-			chan_list_ref(help, "Found chan_list hold call");
-			ast_mutex_unlock(&cl_te_lock);
-			return help;
-		}
-	}
-	ast_mutex_unlock(&cl_te_lock);
-	chan_misdn_log(6, bc->port,
-		"$$$ find_hold_call: No channel found for dialed:%s caller:\"%s\" <%s>\n",
-		bc->dialed.number,
-		bc->caller.name,
-		bc->caller.number);
-
-	return NULL;
-}
-
-
-/*! Returns a reference to the found chan_list. */
-static struct chan_list *find_hold_call_l3(unsigned long l3_id)
-{
-	struct chan_list *help;
-
-	ast_mutex_lock(&cl_te_lock);
-	for (help = cl_te; help; help = help->next) {
-		if (help->hold.state != MISDN_HOLD_IDLE && help->l3id == l3_id) {
-			chan_list_ref(help, "Found chan_list hold call l3");
-			ast_mutex_unlock(&cl_te_lock);
-			return help;
-		}
-	}
-	ast_mutex_unlock(&cl_te_lock);
-
-	return NULL;
-}
-
-#define TRANSFER_ON_HELD_CALL_HANGUP 1
-#if defined(TRANSFER_ON_HELD_CALL_HANGUP)
-/*!
- * \internal
- * \brief Find a suitable active call to go with a held call so we could try a transfer.
- *
- * \param bc B channel record.
- *
- * \return Found call record or NULL.
- *
- * \note Returns a reference to the found chan_list.
- *
- * \note There could be a possibility where we find the wrong active call to transfer.
- * This concern is mitigated by the fact that there could be at most one other call
- * on a PTMP BRI link to another device.  Maybe the l3_id could help in locating an
- * active call on the same TEI?
- */
-static struct chan_list *find_hold_active_call(struct misdn_bchannel *bc)
-{
-	struct chan_list *list;
-
-	ast_mutex_lock(&cl_te_lock);
-	for (list = cl_te; list; list = list->next) {
-		if (list->hold.state == MISDN_HOLD_IDLE && list->bc && list->bc->port == bc->port
-			&& list->ast) {
-			switch (list->state) {
-			case MISDN_PROCEEDING:
-			case MISDN_PROGRESS:
-			case MISDN_ALERTING:
-			case MISDN_CONNECTED:
-				chan_list_ref(list, "Found chan_list hold active call");
-				ast_mutex_unlock(&cl_te_lock);
-				return list;
-			default:
-				break;
-			}
-		}
-	}
-	ast_mutex_unlock(&cl_te_lock);
-	return NULL;
-}
-#endif	/* defined(TRANSFER_ON_HELD_CALL_HANGUP) */
-
-static void cl_queue_chan(struct chan_list *chan)
-{
-	chan_misdn_log(4, chan->bc ? chan->bc->port : 0, "* Queuing chan %p\n", chan);
-
-	chan_list_ref(chan, "Adding chan_list to list");
-	ast_mutex_lock(&cl_te_lock);
-	chan->next = NULL;
-	if (!cl_te) {
-		/* List is empty, make head of list. */
-		cl_te = chan;
-	} else {
-		struct chan_list *help;
-
-		/* Put at end of list. */
-		for (help = cl_te; help->next; help = help->next) {
-		}
-		help->next = chan;
-	}
-	ast_mutex_unlock(&cl_te_lock);
-}
-
-static int cl_dequeue_chan(struct chan_list *chan)
-{
-	int found_it;
-	struct chan_list *help;
-
-	ast_mutex_lock(&cl_te_lock);
-	if (!cl_te) {
-		/* List is empty. */
-		ast_mutex_unlock(&cl_te_lock);
-		return 0;
-	}
-
-	if (cl_te == chan) {
-		/* What we want is the head of the list. */
-		cl_te = cl_te->next;
-		ast_mutex_unlock(&cl_te_lock);
-		chan_list_unref(chan, "Removed chan_list from list head");
-		return 1;
-	}
-
-	found_it = 0;
-	for (help = cl_te; help->next; help = help->next) {
-		if (help->next == chan) {
-			/* Found it in the list. */
-			help->next = help->next->next;
-			found_it = 1;
-			break;
-		}
-	}
-
-	ast_mutex_unlock(&cl_te_lock);
-	if (found_it) {
-		chan_list_unref(chan, "Removed chan_list from list");
-	}
-	return found_it;
-}
-
-/** Channel Queue End **/
-
-
-static int pbx_start_chan(struct chan_list *ch)
-{
-	int ret = ast_pbx_start(ch->ast);
-
-	ch->need_hangup = (ret >= 0) ? 0 : 1;
-
-	return ret;
-}
-
-static void hangup_chan(struct chan_list *ch, struct misdn_bchannel *bc)
-{
-	int port = bc->port;
-
-	if (!ch) {
-		cb_log(1, port, "Cannot hangup chan, no ch\n");
-		return;
-	}
-
-	cb_log(5, port, "hangup_chan called\n");
-
-	if (ch->need_hangup) {
-		cb_log(2, port, " --> hangup\n");
-		ch->need_hangup = 0;
-		ch->need_queue_hangup = 0;
-		if (ch->ast && send_cause2ast(ch->ast, bc, ch)) {
-			ast_hangup(ch->ast);
-		}
-		return;
-	}
-
-	if (!ch->need_queue_hangup) {
-		cb_log(2, port, " --> No need to queue hangup\n");
-		return;
-	}
-
-	ch->need_queue_hangup = 0;
-	if (ch->ast) {
-		if (send_cause2ast(ch->ast, bc, ch)) {
-			ast_queue_hangup_with_cause(ch->ast, bc->cause);
-			cb_log(2, port, " --> queue_hangup\n");
-		}
-	} else {
-		cb_log(1, port, "Cannot hangup chan, no ast\n");
-	}
-}
-
-/*!
- * \internal
- * \brief ISDN asked us to release channel, pendant to misdn_hangup.
- *
- * \param ch Call channel record to release.
- * \param bc Current B channel record associated with ch.
- *
- * \note The only valid thing to do with ch after calling is to chan_list_unref(ch, "").
- */
-static void release_chan(struct chan_list *ch, struct misdn_bchannel *bc)
-{
-	struct ast_channel *ast;
-
-	chan_misdn_log(5, bc->port, "release_chan: bc with pid:%d l3id: %x\n", bc->pid, bc->l3_id);
-
-	ast_mutex_lock(&release_lock);
-	for (;;) {
-		ast = ch->ast;
-		if (!ast || !ast_channel_trylock(ast)) {
-			break;
-		}
-		DEADLOCK_AVOIDANCE(&release_lock);
-	}
-	if (!cl_dequeue_chan(ch)) {
-		/* Someone already released it. */
-		if (ast) {
-			ast_channel_unlock(ast);
-		}
-		ast_mutex_unlock(&release_lock);
-		return;
-	}
-	ch->state = MISDN_CLEANING;
-	ch->ast = NULL;
-	if (ast) {
-		struct chan_list *ast_ch;
-
-		ast_ch = MISDN_ASTERISK_TECH_PVT(ast);
-		MISDN_ASTERISK_TECH_PVT_SET(ast, NULL);
-		chan_misdn_log(1, bc->port,
-			"* RELEASING CHANNEL pid:%d context:%s dialed:%s caller:\"%s\" <%s>\n",
-			bc->pid,
-			ast_channel_context(ast),
-			ast_channel_exten(ast),
-			S_COR(ast_channel_caller(ast)->id.name.valid, ast_channel_caller(ast)->id.name.str, ""),
-			S_COR(ast_channel_caller(ast)->id.number.valid, ast_channel_caller(ast)->id.number.str, ""));
-
-		if (ast_channel_state(ast) != AST_STATE_RESERVED) {
-			chan_misdn_log(3, bc->port, " --> Setting AST State to down\n");
-			ast_setstate(ast, AST_STATE_DOWN);
-		}
-		ast_channel_unlock(ast);
-		if (ast_ch) {
-			chan_list_unref(ast_ch, "Release ast_channel reference.");
-		}
-	}
-
-	if (ch->originator == ORG_AST) {
-		--misdn_out_calls[bc->port];
-	} else {
-		--misdn_in_calls[bc->port];
-	}
-
-	ast_mutex_unlock(&release_lock);
-}
-
-/*!
- * \internal
- * \brief Do everything in release_chan() that makes sense without a bc.
- *
- * \param ch Call channel record to release.
- *
- * \note The only valid thing to do with ch after calling is to chan_list_unref(ch, "").
- */
-static void release_chan_early(struct chan_list *ch)
-{
-	struct ast_channel *ast;
-
-	ast_mutex_lock(&release_lock);
-	for (;;) {
-		ast = ch->ast;
-		if (!ast || !ast_channel_trylock(ast)) {
-			break;
-		}
-		DEADLOCK_AVOIDANCE(&release_lock);
-	}
-	if (!cl_dequeue_chan(ch)) {
-		/* Someone already released it. */
-		if (ast) {
-			ast_channel_unlock(ast);
-		}
-		ast_mutex_unlock(&release_lock);
-		return;
-	}
-	ch->state = MISDN_CLEANING;
-	ch->ast = NULL;
-	if (ast) {
-		struct chan_list *ast_ch;
-
-		ast_ch = MISDN_ASTERISK_TECH_PVT(ast);
-		MISDN_ASTERISK_TECH_PVT_SET(ast, NULL);
-
-		if (ast_channel_state(ast) != AST_STATE_RESERVED) {
-			ast_setstate(ast, AST_STATE_DOWN);
-		}
-		ast_channel_unlock(ast);
-		if (ast_ch) {
-			chan_list_unref(ast_ch, "Release ast_channel reference.");
-		}
-	}
-
-	if (ch->hold.state != MISDN_HOLD_IDLE) {
-		if (ch->originator == ORG_AST) {
-			--misdn_out_calls[ch->hold.port];
-		} else {
-			--misdn_in_calls[ch->hold.port];
-		}
-	}
-
-	ast_mutex_unlock(&release_lock);
-}
-
-/*!
- * \internal
- * \brief Attempt to transfer the active channel party to the held channel party.
- *
- * \param active_ch Channel currently connected.
- * \param held_ch Channel currently on hold.
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int misdn_attempt_transfer(struct chan_list *active_ch, struct chan_list *held_ch)
-{
-	int retval;
-	enum ast_transfer_result xfer_res;
-	struct ast_channel *to_target;
-	struct ast_channel *to_transferee;
-
-	switch (active_ch->state) {
-	case MISDN_PROCEEDING:
-	case MISDN_PROGRESS:
-	case MISDN_ALERTING:
-	case MISDN_CONNECTED:
-		break;
-	default:
-		return -1;
-	}
-
-	ast_channel_lock_both(held_ch->ast, active_ch->ast);
-	to_target = active_ch->ast;
-	to_transferee = held_ch->ast;
-	chan_misdn_log(1, held_ch->hold.port, "TRANSFERRING %s to %s\n",
-		ast_channel_name(to_transferee), ast_channel_name(to_target));
-	held_ch->hold.state = MISDN_HOLD_TRANSFER;
-	ast_channel_ref(to_target);
-	ast_channel_ref(to_transferee);
-	ast_channel_unlock(to_target);
-	ast_channel_unlock(to_transferee);
-
-	retval = 0;
-	xfer_res = ast_bridge_transfer_attended(to_transferee, to_target);
-	if (xfer_res != AST_BRIDGE_TRANSFER_SUCCESS) {
-		retval = -1;
-	}
-
-	ast_channel_unref(to_target);
-	ast_channel_unref(to_transferee);
-	return retval;
-}
-
-
-static void do_immediate_setup(struct misdn_bchannel *bc, struct chan_list *ch, struct ast_channel *ast)
-{
-	char *predial;
-	struct ast_frame fr;
-
-	predial = ast_strdupa(ast_channel_exten(ast));
-
-	ch->state = MISDN_DIALING;
-
-	if (!ch->noautorespond_on_setup) {
-		if (bc->nt) {
-			misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE);
-		} else {
-			if (misdn_lib_is_ptp(bc->port)) {
-				misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE);
-			} else {
-				misdn_lib_send_event(bc, EVENT_PROCEEDING);
-			}
-		}
-	} else {
-		ch->state = MISDN_INCOMING_SETUP;
-	}
-
-	chan_misdn_log(1, bc->port,
-		"* Starting Ast context:%s dialed:%s caller:\"%s\" <%s> with 's' extension\n",
-		ast_channel_context(ast),
-		ast_channel_exten(ast),
-		(ast_channel_caller(ast)->id.name.valid && ast_channel_caller(ast)->id.name.str)
-			? ast_channel_caller(ast)->id.name.str : "",
-		(ast_channel_caller(ast)->id.number.valid && ast_channel_caller(ast)->id.number.str)
-			? ast_channel_caller(ast)->id.number.str : "");
-
-	ast_channel_exten_set(ast, "s");
-
-	if (!ast_canmatch_extension(ast, ast_channel_context(ast), ast_channel_exten(ast), 1, bc->caller.number) || pbx_start_chan(ch) < 0) {
-		ast = NULL;
-		bc->out_cause = AST_CAUSE_UNALLOCATED;
-		hangup_chan(ch, bc);
-		hanguptone_indicate(ch);
-
-		misdn_lib_send_event(bc, bc->nt ? EVENT_RELEASE_COMPLETE : EVENT_DISCONNECT);
-	}
-
-
-	while (!ast_strlen_zero(predial)) {
-		fr.frametype = AST_FRAME_DTMF;
-		fr.subclass.integer = *predial;
-		fr.src = NULL;
-		fr.data.ptr = NULL;
-		fr.datalen = 0;
-		fr.samples = 0;
-		fr.mallocd = 0;
-		fr.offset = 0;
-		fr.delivery = ast_tv(0,0);
-
-		if (ch->ast && MISDN_ASTERISK_TECH_PVT(ch->ast)) {
-			ast_queue_frame(ch->ast, &fr);
-		}
-		predial++;
-	}
-}
-
-/*!
- * \retval -1 if can hangup after calling.
- * \retval 0 if cannot hangup after calling.
- */
-static int send_cause2ast(struct ast_channel *ast, struct misdn_bchannel *bc, struct chan_list *ch)
-{
-	int can_hangup;
-
-	if (!ast) {
-		chan_misdn_log(1, 0, "send_cause2ast: No Ast\n");
-		return 0;
-	}
-	if (!bc) {
-		chan_misdn_log(1, 0, "send_cause2ast: No BC\n");
-		return 0;
-	}
-	if (!ch) {
-		chan_misdn_log(1, 0, "send_cause2ast: No Ch\n");
-		return 0;
-	}
-
-	ast_channel_hangupcause_set(ast, bc->cause);
-
-	can_hangup = -1;
-	switch (bc->cause) {
-	case AST_CAUSE_UNALLOCATED:
-	case AST_CAUSE_NO_ROUTE_TRANSIT_NET:
-	case AST_CAUSE_NO_ROUTE_DESTINATION:
- 	case 4:	/* Send special information tone */
- 	case AST_CAUSE_NUMBER_CHANGED:
- 	case AST_CAUSE_DESTINATION_OUT_OF_ORDER:
-		/* Congestion Cases */
-		/*
-		 * Not Queueing the Congestion anymore, since we want to hear
-		 * the inband message
-		 *
-		chan_misdn_log(1, bc ? bc->port : 0, " --> * SEND: Queue Congestion pid:%d\n", bc ? bc->pid : -1);
-		ch->state = MISDN_BUSY;
-
-		ast_queue_control(ast, AST_CONTROL_CONGESTION);
-		*/
-		break;
-
-	case AST_CAUSE_CALL_REJECTED:
-	case AST_CAUSE_USER_BUSY:
-		ch->state = MISDN_BUSY;
-
-		if (!ch->need_busy) {
-			chan_misdn_log(1, bc ? bc->port : 0, "Queued busy already\n");
-			break;
-		}
-		ch->need_busy = 0;
-
-		chan_misdn_log(1, bc ? bc->port : 0, " --> * SEND: Queue Busy pid:%d\n", bc ? bc->pid : -1);
-		ast_queue_control(ast, AST_CONTROL_BUSY);
-
-		/* The BUSY is likely to cause a hangup or the user needs to hear it. */
-		can_hangup = 0;
-		break;
-	}
-	return can_hangup;
-}
-
-
-/*! \brief Import parameters from the dialplan environment variables */
-void import_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch)
-{
-	const char *tmp;
-
-	ast_channel_lock(chan);
-	tmp = pbx_builtin_getvar_helper(chan, "MISDN_ADDRESS_COMPLETE");
-	if (tmp && (atoi(tmp) == 1)) {
-		bc->sending_complete = 1;
-	}
-
-	tmp = pbx_builtin_getvar_helper(chan, "MISDN_USERUSER");
-	if (tmp) {
-		ast_log(LOG_NOTICE, "MISDN_USERUSER: %s\n", tmp);
-		ast_copy_string(bc->uu, tmp, sizeof(bc->uu));
-		bc->uulen = strlen(bc->uu);
-	}
-
-	tmp = pbx_builtin_getvar_helper(chan, "MISDN_KEYPAD");
-	if (tmp) {
-		ast_copy_string(bc->keypad, tmp, sizeof(bc->keypad));
-	}
-	ast_channel_unlock(chan);
-}
-
-/*! \brief Export parameters to the dialplan environment variables */
-void export_ch(struct ast_channel *chan, struct misdn_bchannel *bc, struct chan_list *ch)
-{
-	char tmp[32];
-
-	/*
-	 * The only use for MISDN_PID is if there is a problem and you
-	 * have to use the "misdn restart pid" CLI command.  Otherwise,
-	 * the pid is not used by anyone.  The internal use of MISDN_PID
-	 * has been deleted.
-	 */
-	chan_misdn_log(3, bc->port, " --> EXPORT_PID: pid:%d\n", bc->pid);
-	snprintf(tmp, sizeof(tmp), "%d", bc->pid);
-	pbx_builtin_setvar_helper(chan, "_MISDN_PID", tmp);
-
-	if (bc->sending_complete) {
-		snprintf(tmp, sizeof(tmp), "%d", bc->sending_complete);
-		pbx_builtin_setvar_helper(chan, "MISDN_ADDRESS_COMPLETE", tmp);
-	}
-
-	if (bc->urate) {
-		snprintf(tmp, sizeof(tmp), "%d", bc->urate);
-		pbx_builtin_setvar_helper(chan, "MISDN_URATE", tmp);
-	}
-
-	if (bc->uulen) {
-		pbx_builtin_setvar_helper(chan, "MISDN_USERUSER", bc->uu);
-	}
-
-	if (!ast_strlen_zero(bc->keypad)) {
-		pbx_builtin_setvar_helper(chan, "MISDN_KEYPAD", bc->keypad);
-	}
-}
-
-int add_in_calls(int port)
-{
-	int max_in_calls;
-
-	misdn_cfg_get(port, MISDN_CFG_MAX_IN, &max_in_calls, sizeof(max_in_calls));
-	misdn_in_calls[port]++;
-
-	if (max_in_calls >= 0 && max_in_calls < misdn_in_calls[port]) {
-		ast_log(LOG_NOTICE, "Marking Incoming Call on port[%d]\n", port);
-		return misdn_in_calls[port] - max_in_calls;
-	}
-
-	return 0;
-}
-
-int add_out_calls(int port)
-{
-	int max_out_calls;
-
-	misdn_cfg_get(port, MISDN_CFG_MAX_OUT, &max_out_calls, sizeof(max_out_calls));
-
-	if (max_out_calls >= 0 && max_out_calls <= misdn_out_calls[port]) {
-		ast_log(LOG_NOTICE, "Rejecting Outgoing Call on port[%d]\n", port);
-		return (misdn_out_calls[port] + 1) - max_out_calls;
-	}
-
-	misdn_out_calls[port]++;
-
-	return 0;
-}
-
-static void start_pbx(struct chan_list *ch, struct misdn_bchannel *bc, struct ast_channel *chan)
-{
-	if (pbx_start_chan(ch) < 0) {
-		hangup_chan(ch, bc);
-		chan_misdn_log(-1, bc->port, "ast_pbx_start returned <0 in SETUP\n");
-		if (bc->nt) {
-			hanguptone_indicate(ch);
-			misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE);
-		} else {
-			misdn_lib_send_event(bc, EVENT_RELEASE);
-		}
-	}
-}
-
-static void wait_for_digits(struct chan_list *ch, struct misdn_bchannel *bc, struct ast_channel *chan)
-{
-	ch->state = MISDN_WAITING4DIGS;
-	misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE);
-	if (bc->nt && !bc->dialed.number[0]) {
-		dialtone_indicate(ch);
-	}
-}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Handle the FACILITY CCBSStatusRequest message.
- *
- * \param port Logical port number.
- * \param facility Facility ie contents.
- */
-static void misdn_cc_handle_ccbs_status_request(int port, const struct FacParm *facility)
-{
-	struct misdn_cc_record *cc_record;
-	struct misdn_bchannel dummy;
-
-	switch (facility->u.CCBSStatusRequest.ComponentType) {
-	case FacComponent_Invoke:
-		/* Build message */
-		misdn_make_dummy(&dummy, port, 0, misdn_lib_port_is_nt(port), 0);
-		dummy.fac_out.Function = Fac_CCBSStatusRequest;
-		dummy.fac_out.u.CCBSStatusRequest.InvokeID = facility->u.CCBSStatusRequest.InvokeID;
-		dummy.fac_out.u.CCBSStatusRequest.ComponentType = FacComponent_Result;
-
-		/* Answer User-A free question */
-		AST_LIST_LOCK(&misdn_cc_records_db);
-		cc_record = misdn_cc_find_by_reference(port, facility->u.CCBSStatusRequest.Component.Invoke.CCBSReference);
-		if (cc_record) {
-			dummy.fac_out.u.CCBSStatusRequest.Component.Result.Free = cc_record->party_a_free;
-		} else {
-			/* No record so say User-A is free */
-			dummy.fac_out.u.CCBSStatusRequest.Component.Result.Free = 1;
-		}
-		AST_LIST_UNLOCK(&misdn_cc_records_db);
-
-		/* Send message */
-		print_facility(&dummy.fac_out, &dummy);
-		misdn_lib_send_event(&dummy, EVENT_FACILITY);
-		break;
-
-	default:
-		chan_misdn_log(0, port, " --> not yet handled: facility type:0x%04X\n", facility->Function);
-		break;
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Start a PBX to notify that User-B is available.
- *
- * \param record_id Call completion record ID
- * \param notify Dialplan location to start processing.
- */
-static void misdn_cc_pbx_notify(long record_id, const struct misdn_cc_notify *notify)
-{
-	struct ast_channel *chan;
-	char id_str[32];
-
-	static unsigned short sequence = 0;
-
-	/* Create a channel to notify with */
-	snprintf(id_str, sizeof(id_str), "%ld", record_id);
-	chan = ast_channel_alloc(0, AST_STATE_DOWN, id_str, NULL, NULL,
-		notify->exten, notify->context, NULL, 0,
-		"mISDN-CC/%ld-%X", record_id, (unsigned) ++sequence);
-	if (!chan) {
-		ast_log(LOG_ERROR, "Unable to allocate channel!\n");
-		return;
-	}
-	ast_channel_priority_set(chan, notify->priority);
-	ast_free(ast_channel_dialed(chan)->number.str);
-	ast_channel_dialed(chan)->number.str = ast_strdup(notify->exten);
-
-	ast_channel_unlock(chan);
-
-	if (ast_pbx_start(chan)) {
-		ast_log(LOG_WARNING, "Unable to start pbx channel %s!\n", ast_channel_name(chan));
-		ast_channel_release(chan);
-	} else {
-		ast_verb(1, "Started pbx for call completion notify channel %s\n", ast_channel_name(chan));
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Handle the FACILITY CCBS_T_RemoteUserFree message.
- *
- * \param bc B channel control structure message came in on
- */
-static void misdn_cc_handle_T_remote_user_free(struct misdn_bchannel *bc)
-{
-	struct misdn_cc_record *cc_record;
-	struct misdn_cc_notify notify;
-	long record_id;
-
-	AST_LIST_LOCK(&misdn_cc_records_db);
-	cc_record = misdn_cc_find_by_bc(bc);
-	if (cc_record) {
-		if (cc_record->party_a_free) {
-			notify = cc_record->remote_user_free;
-		} else {
-			/* Send CCBS_T_Suspend message */
-			bc->fac_out.Function = Fac_CCBS_T_Suspend;
-			bc->fac_out.u.CCBS_T_Suspend.InvokeID = ++misdn_invoke_id;
-			print_facility(&bc->fac_out, bc);
-			misdn_lib_send_event(bc, EVENT_FACILITY);
-
-			notify = cc_record->b_free;
-		}
-		record_id = cc_record->record_id;
-		AST_LIST_UNLOCK(&misdn_cc_records_db);
-		if (notify.context[0]) {
-			/* Party A is free or B-Free notify has been setup. */
-			misdn_cc_pbx_notify(record_id, &notify);
-		}
-	} else {
-		AST_LIST_UNLOCK(&misdn_cc_records_db);
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Handle the FACILITY CCBSRemoteUserFree message.
- *
- * \param port Logical port number.
- * \param facility Facility ie contents.
- */
-static void misdn_cc_handle_remote_user_free(int port, const struct FacParm *facility)
-{
-	struct misdn_cc_record *cc_record;
-	struct misdn_cc_notify notify;
-	long record_id;
-
-	AST_LIST_LOCK(&misdn_cc_records_db);
-	cc_record = misdn_cc_find_by_reference(port, facility->u.CCBSRemoteUserFree.CCBSReference);
-	if (cc_record) {
-		notify = cc_record->remote_user_free;
-		record_id = cc_record->record_id;
-		AST_LIST_UNLOCK(&misdn_cc_records_db);
-		misdn_cc_pbx_notify(record_id, &notify);
-	} else {
-		AST_LIST_UNLOCK(&misdn_cc_records_db);
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Handle the FACILITY CCBSBFree message.
- *
- * \param port Logical port number.
- * \param facility Facility ie contents.
- */
-static void misdn_cc_handle_b_free(int port, const struct FacParm *facility)
-{
-	struct misdn_cc_record *cc_record;
-	struct misdn_cc_notify notify;
-	long record_id;
-
-	AST_LIST_LOCK(&misdn_cc_records_db);
-	cc_record = misdn_cc_find_by_reference(port, facility->u.CCBSBFree.CCBSReference);
-	if (cc_record && cc_record->b_free.context[0]) {
-		/* B-Free notify has been setup. */
-		notify = cc_record->b_free;
-		record_id = cc_record->record_id;
-		AST_LIST_UNLOCK(&misdn_cc_records_db);
-		misdn_cc_pbx_notify(record_id, &notify);
-	} else {
-		AST_LIST_UNLOCK(&misdn_cc_records_db);
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-/*!
- * \internal
- * \brief Handle the incoming facility ie contents
- *
- * \param event Message type facility ie came in on
- * \param bc B channel control structure message came in on
- * \param ch Associated channel call record
- */
-static void misdn_facility_ie_handler(enum event_e event, struct misdn_bchannel *bc, struct chan_list *ch)
-{
-#if defined(AST_MISDN_ENHANCEMENTS)
-	const char *diagnostic_msg;
-	struct misdn_cc_record *cc_record;
-	char buf[32];
-	struct misdn_party_id party_id;
-	long new_record_id;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-	print_facility(&bc->fac_in, bc);
-	switch (bc->fac_in.Function) {
-#if defined(AST_MISDN_ENHANCEMENTS)
-	case Fac_ActivationDiversion:
-		switch (bc->fac_in.u.ActivationDiversion.ComponentType) {
-		case FacComponent_Result:
-			/* Positive ACK to activation */
-			/* We don't handle this yet */
-			break;
-		default:
-			chan_misdn_log(0, bc->port," --> not yet handled: facility type:0x%04X\n",
-				bc->fac_in.Function);
-			break;
-		}
-		break;
-	case Fac_DeactivationDiversion:
-		switch (bc->fac_in.u.DeactivationDiversion.ComponentType) {
-		case FacComponent_Result:
-			/* Positive ACK to deactivation */
-			/* We don't handle this yet */
-			break;
-		default:
-			chan_misdn_log(0, bc->port," --> not yet handled: facility type:0x%04X\n",
-				bc->fac_in.Function);
-			break;
-		}
-		break;
-	case Fac_ActivationStatusNotificationDiv:
-		/* Sent to other MSN numbers on the line when a user activates call forwarding. */
-		/* Sent in the first call control message of an outgoing call from the served user. */
-		/* We do not have anything to do for this message. */
-		break;
-	case Fac_DeactivationStatusNotificationDiv:
-		/* Sent to other MSN numbers on the line when a user deactivates call forwarding. */
-		/* We do not have anything to do for this message. */
-		break;
-#if 0	/* We don't handle this yet */
-	case Fac_InterrogationDiversion:
-		/* We don't handle this yet */
-		break;
-	case Fac_InterrogateServedUserNumbers:
-		/* We don't handle this yet */
-		break;
-#endif	/* We don't handle this yet */
-	case Fac_DiversionInformation:
-		/* Sent to the served user when a call is forwarded. */
-		/* We do not have anything to do for this message. */
-		break;
-	case Fac_CallDeflection:
-		if (ch && ch->ast) {
-			switch (bc->fac_in.u.CallDeflection.ComponentType) {
-			case FacComponent_Invoke:
-				ast_copy_string(bc->redirecting.from.number, bc->dialed.number,
-					sizeof(bc->redirecting.from.number));
-				bc->redirecting.from.name[0] = 0;
-				bc->redirecting.from.number_plan = bc->dialed.number_plan;
-				bc->redirecting.from.number_type = bc->dialed.number_type;
-				bc->redirecting.from.screening = 0;/* Unscreened */
-				if (bc->fac_in.u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUserPresent) {
-					bc->redirecting.from.presentation =
-						bc->fac_in.u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUser
-						? 0 /* Allowed */ : 1 /* Restricted */;
-				} else {
-					bc->redirecting.from.presentation = 0;/* Allowed */
-				}
-
-				/* Add configured prefix to the call deflection number */
-				memset(&party_id, 0, sizeof(party_id));
-				misdn_PartyNumber_extract(&party_id,
-					&bc->fac_in.u.CallDeflection.Component.Invoke.Deflection.Party);
-				misdn_add_number_prefix(bc->port, party_id.number_type,
-					party_id.number, sizeof(party_id.number));
-				//party_id.presentation = 0;/* Allowed */
-				//party_id.screening = 0;/* Unscreened */
-				bc->redirecting.to = party_id;
-
-				++bc->redirecting.count;
-				bc->redirecting.reason = mISDN_REDIRECTING_REASON_DEFLECTION;
-
-				misdn_copy_redirecting_to_ast(ch->ast, &bc->redirecting, bc->incoming_cid_tag);
-				ast_channel_call_forward_set(ch->ast, bc->redirecting.to.number);
-
-				/* Send back positive ACK */
-#if 1
-				/*
-				 * Since there are no return result arguments it must be a
-				 * generic result message.  ETSI 300-196
-				 */
-				bc->fac_out.Function = Fac_RESULT;
-				bc->fac_out.u.RESULT.InvokeID = bc->fac_in.u.CallDeflection.InvokeID;
-#else
-				bc->fac_out.Function = Fac_CallDeflection;
-				bc->fac_out.u.CallDeflection.InvokeID = bc->fac_in.u.CallDeflection.InvokeID;
-				bc->fac_out.u.CallDeflection.ComponentType = FacComponent_Result;
-#endif
-				print_facility(&bc->fac_out, bc);
-				misdn_lib_send_event(bc, EVENT_DISCONNECT);
-
-				/* This line is BUSY to further attempts by this dialing attempt. */
-				ast_queue_control(ch->ast, AST_CONTROL_BUSY);
-				break;
-
-			case FacComponent_Result:
-				/* Positive ACK to call deflection */
-				/*
-				 * Sent in DISCONNECT or FACILITY message depending upon network option.
-				 * It is in the FACILITY message if the call is still offered to the user
-				 * while trying to alert the deflected to party.
-				 */
-				/* Ignore the ACK */
-				break;
-
-			default:
-				break;
-			}
-		}
-		break;
-#if 0	/* We don't handle this yet */
-	case Fac_CallRerouteing:
-		/* Private-Public ISDN interworking message */
-		/* We don't handle this yet */
-		break;
-#endif	/* We don't handle this yet */
-	case Fac_DivertingLegInformation1:
-		/* Private-Public ISDN interworking message */
-		bc->div_leg_3_rx_wanted = 0;
-		if (ch && ch->ast) {
-			bc->redirecting.reason =
-				diversion_reason_to_misdn(bc->fac_in.u.DivertingLegInformation1.DiversionReason);
-			if (bc->fac_in.u.DivertingLegInformation1.DivertedToPresent) {
-				misdn_PresentedNumberUnscreened_extract(&bc->redirecting.to,
-					&bc->fac_in.u.DivertingLegInformation1.DivertedTo);
-
-				/* Add configured prefix to redirecting.to.number */
-				misdn_add_number_prefix(bc->port, bc->redirecting.to.number_type,
-					bc->redirecting.to.number, sizeof(bc->redirecting.to.number));
-			} else {
-				bc->redirecting.to.number[0] = '\0';
-				bc->redirecting.to.number_plan = NUMPLAN_ISDN;
-				bc->redirecting.to.number_type = NUMTYPE_UNKNOWN;
-				bc->redirecting.to.presentation = 1;/* restricted */
-				bc->redirecting.to.screening = 0;/* unscreened */
-			}
-			misdn_copy_redirecting_to_ast(ch->ast, &bc->redirecting, bc->incoming_cid_tag);
-			bc->div_leg_3_rx_wanted = 1;
-		}
-		break;
-	case Fac_DivertingLegInformation2:
-		/* Private-Public ISDN interworking message */
-		switch (event) {
-		case EVENT_SETUP:
-			/* Comes in on a SETUP with redirecting.from information */
-			bc->div_leg_3_tx_pending = 1;
-			if (ch && ch->ast) {
-				/*
-				 * Setup the redirecting.to informtion so we can identify
-				 * if the user wants to manually supply the COLR for this
-				 * redirected to number if further redirects could happen.
-				 *
-				 * All the user needs to do is set the REDIRECTING(to-pres)
-				 * to the COLR and REDIRECTING(to-num) = ${EXTEN} to be safe
-				 * after determining that the incoming call was redirected by
-				 * checking if there is a REDIRECTING(from-num).
-				 */
-				ast_copy_string(bc->redirecting.to.number, bc->dialed.number,
-					sizeof(bc->redirecting.to.number));
-				bc->redirecting.to.number_plan = bc->dialed.number_plan;
-				bc->redirecting.to.number_type = bc->dialed.number_type;
-				bc->redirecting.to.presentation = 1;/* restricted */
-				bc->redirecting.to.screening = 0;/* unscreened */
-
-				bc->redirecting.reason =
-					diversion_reason_to_misdn(bc->fac_in.u.DivertingLegInformation2.DiversionReason);
-				bc->redirecting.count = bc->fac_in.u.DivertingLegInformation2.DiversionCounter;
-				if (bc->fac_in.u.DivertingLegInformation2.DivertingPresent) {
-					/* This information is redundant if there was a redirecting ie in the SETUP. */
-					misdn_PresentedNumberUnscreened_extract(&bc->redirecting.from,
-						&bc->fac_in.u.DivertingLegInformation2.Diverting);
-
-					/* Add configured prefix to redirecting.from.number */
-					misdn_add_number_prefix(bc->port, bc->redirecting.from.number_type,
-						bc->redirecting.from.number, sizeof(bc->redirecting.from.number));
-				}
-#if 0
-				if (bc->fac_in.u.DivertingLegInformation2.OriginalCalledPresent) {
-					/* We have no place to put the OriginalCalled number */
-				}
-#endif
-				misdn_copy_redirecting_to_ast(ch->ast, &bc->redirecting, bc->incoming_cid_tag);
-			}
-			break;
-		default:
-			chan_misdn_log(0, bc->port," --> Expected in a SETUP message: facility type:0x%04X\n",
-				bc->fac_in.Function);
-			break;
-		}
-		break;
-	case Fac_DivertingLegInformation3:
-		/* Private-Public ISDN interworking message */
-		if (bc->div_leg_3_rx_wanted) {
-			bc->div_leg_3_rx_wanted = 0;
-
-			if (ch && ch->ast) {
-				struct ast_party_redirecting redirecting;
-
-				ast_channel_redirecting(ch->ast)->to.number.presentation =
-					bc->fac_in.u.DivertingLegInformation3.PresentationAllowedIndicator
-					? AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_UNSCREENED
-					: AST_PRES_RESTRICTED | AST_PRES_USER_NUMBER_UNSCREENED;
-				ast_party_redirecting_init(&redirecting);
-				ast_party_redirecting_copy(&redirecting, ast_channel_redirecting(ch->ast));
-
-				/*
-				 * Reset any earlier private redirecting id representations and
-				 * make sure that it is invalidated at the remote end.
-				 */
-				ast_party_id_reset(&redirecting.priv_orig);
-				ast_party_id_reset(&redirecting.priv_from);
-				ast_party_id_reset(&redirecting.priv_to);
-
-				ast_channel_queue_redirecting_update(ch->ast, &redirecting, NULL);
-				ast_party_redirecting_free(&redirecting);
-			}
-		}
-		break;
-
-#else	/* !defined(AST_MISDN_ENHANCEMENTS) */
-
-	case Fac_CD:
-		if (ch && ch->ast) {
-			ast_copy_string(bc->redirecting.from.number, bc->dialed.number,
-				sizeof(bc->redirecting.from.number));
-			bc->redirecting.from.name[0] = 0;
-			bc->redirecting.from.number_plan = bc->dialed.number_plan;
-			bc->redirecting.from.number_type = bc->dialed.number_type;
-			bc->redirecting.from.screening = 0;/* Unscreened */
-			bc->redirecting.from.presentation =
-				bc->fac_in.u.CDeflection.PresentationAllowed
-				? 0 /* Allowed */ : 1 /* Restricted */;
-
-			ast_copy_string(bc->redirecting.to.number,
-				(char *) bc->fac_in.u.CDeflection.DeflectedToNumber,
-				sizeof(bc->redirecting.to.number));
-			bc->redirecting.to.name[0] = 0;
-			bc->redirecting.to.number_plan = NUMPLAN_UNKNOWN;
-			bc->redirecting.to.number_type = NUMTYPE_UNKNOWN;
-			bc->redirecting.to.presentation = 0;/* Allowed */
-			bc->redirecting.to.screening = 0;/* Unscreened */
-
-			++bc->redirecting.count;
-			bc->redirecting.reason = mISDN_REDIRECTING_REASON_DEFLECTION;
-
-			misdn_copy_redirecting_to_ast(ch->ast, &bc->redirecting, bc->incoming_cid_tag);
-			ast_channel_call_forward_set(ch->ast, bc->redirecting.to.number);
-
-			misdn_lib_send_event(bc, EVENT_DISCONNECT);
-
-			/* This line is BUSY to further attempts by this dialing attempt. */
-			ast_queue_control(ch->ast, AST_CONTROL_BUSY);
-		}
-		break;
-#endif	/* !defined(AST_MISDN_ENHANCEMENTS) */
-	case Fac_AOCDCurrency:
-		if (ch && ch->ast) {
-			bc->AOCDtype = Fac_AOCDCurrency;
-			memcpy(&bc->AOCD.currency, &bc->fac_in.u.AOCDcur, sizeof(bc->AOCD.currency));
-			bc->AOCD_need_export = 1;
-			export_aoc_vars(ch->originator, ch->ast, bc);
-		}
-		break;
-	case Fac_AOCDChargingUnit:
-		if (ch && ch->ast) {
-			bc->AOCDtype = Fac_AOCDChargingUnit;
-			memcpy(&bc->AOCD.chargingUnit, &bc->fac_in.u.AOCDchu, sizeof(bc->AOCD.chargingUnit));
-			bc->AOCD_need_export = 1;
-			export_aoc_vars(ch->originator, ch->ast, bc);
-		}
-		break;
-#if defined(AST_MISDN_ENHANCEMENTS)
-	case Fac_ERROR:
-		diagnostic_msg = misdn_to_str_error_code(bc->fac_in.u.ERROR.errorValue);
-		chan_misdn_log(1, bc->port, " --> Facility error code: %s\n", diagnostic_msg);
-		switch (event) {
-		case EVENT_DISCONNECT:
-		case EVENT_RELEASE:
-		case EVENT_RELEASE_COMPLETE:
-			/* Possible call failure as a result of Fac_CCBSCall/Fac_CCBS_T_Call */
-			if (ch && ch->peer) {
-				misdn_cc_set_peer_var(ch->peer, MISDN_ERROR_MSG, diagnostic_msg);
-			}
-			break;
-		default:
-			break;
-		}
-		AST_LIST_LOCK(&misdn_cc_records_db);
-		cc_record = misdn_cc_find_by_invoke(bc->port, bc->fac_in.u.ERROR.invokeId);
-		if (cc_record) {
-			cc_record->outstanding_message = 0;
-			cc_record->error_code = bc->fac_in.u.ERROR.errorValue;
-		}
-		AST_LIST_UNLOCK(&misdn_cc_records_db);
-		break;
-	case Fac_REJECT:
-		diagnostic_msg = misdn_to_str_reject_code(bc->fac_in.u.REJECT.Code);
-		chan_misdn_log(1, bc->port, " --> Facility reject code: %s\n", diagnostic_msg);
-		switch (event) {
-		case EVENT_DISCONNECT:
-		case EVENT_RELEASE:
-		case EVENT_RELEASE_COMPLETE:
-			/* Possible call failure as a result of Fac_CCBSCall/Fac_CCBS_T_Call */
-			if (ch && ch->peer) {
-				misdn_cc_set_peer_var(ch->peer, MISDN_ERROR_MSG, diagnostic_msg);
-			}
-			break;
-		default:
-			break;
-		}
-		if (bc->fac_in.u.REJECT.InvokeIDPresent) {
-			AST_LIST_LOCK(&misdn_cc_records_db);
-			cc_record = misdn_cc_find_by_invoke(bc->port, bc->fac_in.u.REJECT.InvokeID);
-			if (cc_record) {
-				cc_record->outstanding_message = 0;
-				cc_record->reject_code = bc->fac_in.u.REJECT.Code;
-			}
-			AST_LIST_UNLOCK(&misdn_cc_records_db);
-		}
-		break;
-	case Fac_RESULT:
-		AST_LIST_LOCK(&misdn_cc_records_db);
-		cc_record = misdn_cc_find_by_invoke(bc->port, bc->fac_in.u.RESULT.InvokeID);
-		if (cc_record) {
-			cc_record->outstanding_message = 0;
-		}
-		AST_LIST_UNLOCK(&misdn_cc_records_db);
-		break;
-#if 0	/* We don't handle this yet */
-	case Fac_EctExecute:
-		/* We don't handle this yet */
-		break;
-	case Fac_ExplicitEctExecute:
-		/* We don't handle this yet */
-		break;
-	case Fac_EctLinkIdRequest:
-		/* We don't handle this yet */
-		break;
-#endif	/* We don't handle this yet */
-	case Fac_SubaddressTransfer:
-		/* We do not have anything to do for this message since we do not handle subaddresses. */
-		break;
-	case Fac_RequestSubaddress:
-		/*
-		 * We do not have anything to do for this message since we do not handle subaddresses.
-		 * However, we do care about some other ie's that should be present.
-		 */
-		if (bc->redirecting.to_changed) {
-			/* Add configured prefix to redirecting.to.number */
-			misdn_add_number_prefix(bc->port, bc->redirecting.to.number_type,
-				bc->redirecting.to.number, sizeof(bc->redirecting.to.number));
-		}
-		switch (bc->notify_description_code) {
-		case mISDN_NOTIFY_CODE_INVALID:
-			/* Notify ie was not present. */
-			bc->redirecting.to_changed = 0;
-			break;
-		case mISDN_NOTIFY_CODE_CALL_TRANSFER_ALERTING:
-			/*
-			 * It would be preferable to update the connected line information
-			 * only when the message callStatus is active.  However, the
-			 * optional redirection number may not be present in the active
-			 * message if an alerting message were received earlier.
-			 *
-			 * The consequences if we wind up sending two updates is benign.
-			 * The other end will think that it got transferred twice.
-			 */
-			if (!bc->redirecting.to_changed) {
-				break;
-			}
-			bc->redirecting.to_changed = 0;
-			if (!ch || !ch->ast) {
-				break;
-			}
-			misdn_update_remote_party(ch->ast, &bc->redirecting.to,
-				AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING,
-				bc->incoming_cid_tag);
-			break;
-		case mISDN_NOTIFY_CODE_CALL_TRANSFER_ACTIVE:
-			if (!bc->redirecting.to_changed) {
-				break;
-			}
-			bc->redirecting.to_changed = 0;
-			if (!ch || !ch->ast) {
-				break;
-			}
-			misdn_update_remote_party(ch->ast, &bc->redirecting.to,
-				AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, bc->incoming_cid_tag);
-			break;
-		default:
-			bc->redirecting.to_changed = 0;
-			chan_misdn_log(0, bc->port," --> not yet handled: notify code:0x%02X\n",
-				bc->notify_description_code);
-			break;
-		}
-		bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID;
-		break;
-	case Fac_EctInform:
-		/* Private-Public ISDN interworking message */
-		if (ch && ch->ast && bc->fac_in.u.EctInform.RedirectionPresent) {
-			/* Add configured prefix to the redirection number */
-			memset(&party_id, 0, sizeof(party_id));
-			misdn_PresentedNumberUnscreened_extract(&party_id,
-				&bc->fac_in.u.EctInform.Redirection);
-			misdn_add_number_prefix(bc->port, party_id.number_type,
-				party_id.number, sizeof(party_id.number));
-
-			/*
-			 * It would be preferable to update the connected line information
-			 * only when the message callStatus is active.  However, the
-			 * optional redirection number may not be present in the active
-			 * message if an alerting message were received earlier.
-			 *
-			 * The consequences if we wind up sending two updates is benign.
-			 * The other end will think that it got transferred twice.
-			 */
-			misdn_update_remote_party(ch->ast, &party_id,
-				(bc->fac_in.u.EctInform.Status == 0 /* alerting */)
-					? AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING
-					: AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER,
-					bc->incoming_cid_tag);
-		}
-		break;
-#if 0	/* We don't handle this yet */
-	case Fac_EctLoopTest:
-		/* The use of this message is unclear on how it works to detect loops. */
-		/* We don't handle this yet */
-		break;
-#endif	/* We don't handle this yet */
-	case Fac_CallInfoRetain:
-		switch (event) {
-		case EVENT_ALERTING:
-		case EVENT_DISCONNECT:
-			/* CCBS/CCNR is available */
-			if (ch && ch->peer) {
-				AST_LIST_LOCK(&misdn_cc_records_db);
-				if (ch->record_id == -1) {
-					cc_record = misdn_cc_new();
-				} else {
-					/*
-					 * We are doing a call-completion attempt
-					 * or the switch is sending us extra call-completion
-					 * availability indications (erroneously?).
-					 *
-					 * Assume that the network request retention option
-					 * is not on and that the current call-completion
-					 * request is disabled.
-					 */
-					cc_record = misdn_cc_find_by_id(ch->record_id);
-					if (cc_record) {
-						if (cc_record->ptp && cc_record->mode.ptp.bc) {
-							/*
-							 * What?  We are getting mixed messages from the
-							 * switch.  We are currently setup for
-							 * point-to-point.  Now we are switching to
-							 * point-to-multipoint.
-							 *
-							 * Close the call-completion signaling link
-							 */
-							cc_record->mode.ptp.bc->fac_out.Function = Fac_None;
-							cc_record->mode.ptp.bc->out_cause = AST_CAUSE_NORMAL_CLEARING;
-							misdn_lib_send_event(cc_record->mode.ptp.bc, EVENT_RELEASE_COMPLETE);
-						}
-
-						/*
-						 * Resetup the existing record for a possible new
-						 * call-completion request.
-						 */
-						new_record_id = misdn_cc_record_id_new();
-						if (new_record_id < 0) {
-							/* Looks like we must keep the old id anyway. */
-						} else {
-							cc_record->record_id = new_record_id;
-							ch->record_id = new_record_id;
-						}
-						cc_record->ptp = 0;
-						cc_record->port = bc->port;
-						memset(&cc_record->mode, 0, sizeof(cc_record->mode));
-						cc_record->mode.ptmp.linkage_id = bc->fac_in.u.CallInfoRetain.CallLinkageID;
-						cc_record->invoke_id = ++misdn_invoke_id;
-						cc_record->activated = 0;
-						cc_record->outstanding_message = 0;
-						cc_record->activation_requested = 0;
-						cc_record->error_code = FacError_None;
-						cc_record->reject_code = FacReject_None;
-						memset(&cc_record->remote_user_free, 0, sizeof(cc_record->remote_user_free));
-						memset(&cc_record->b_free, 0, sizeof(cc_record->b_free));
-						cc_record->time_created = time(NULL);
-
-						cc_record = NULL;
-					} else {
-						/*
-						 * Where did the record go?  We will have to recapture
-						 * the call setup information.  Unfortunately, some
-						 * setup information may have been changed.
-						 */
-						ch->record_id = -1;
-						cc_record = misdn_cc_new();
-					}
-				}
-				if (cc_record) {
-					ch->record_id = cc_record->record_id;
-					cc_record->ptp = 0;
-					cc_record->port = bc->port;
-					cc_record->mode.ptmp.linkage_id = bc->fac_in.u.CallInfoRetain.CallLinkageID;
-
-					/* Record call information for possible call-completion attempt. */
-					cc_record->redial.caller = bc->caller;
-					cc_record->redial.dialed = bc->dialed;
-					cc_record->redial.setup_bc_hlc_llc = bc->setup_bc_hlc_llc;
-					cc_record->redial.capability = bc->capability;
-					cc_record->redial.hdlc = bc->hdlc;
-				}
-				AST_LIST_UNLOCK(&misdn_cc_records_db);
-
-				/* Set MISDN_CC_RECORD_ID in original channel */
-				if (ch->record_id != -1) {
-					snprintf(buf, sizeof(buf), "%ld", ch->record_id);
-				} else {
-					buf[0] = 0;
-				}
-				misdn_cc_set_peer_var(ch->peer, MISDN_CC_RECORD_ID, buf);
-			}
-			break;
-		default:
-			chan_misdn_log(0, bc->port,
-				" --> Expected in a DISCONNECT or ALERTING message: facility type:0x%04X\n",
-				bc->fac_in.Function);
-			break;
-		}
-		break;
-	case Fac_CCBS_T_Call:
-	case Fac_CCBSCall:
-		switch (event) {
-		case EVENT_SETUP:
-			/*
-			 * This is a call completion retry call.
-			 * If we had anything to do we would do it here.
-			 */
-			break;
-		default:
-			chan_misdn_log(0, bc->port, " --> Expected in a SETUP message: facility type:0x%04X\n",
-				bc->fac_in.Function);
-			break;
-		}
-		break;
-	case Fac_CCBSDeactivate:
-		switch (bc->fac_in.u.CCBSDeactivate.ComponentType) {
-		case FacComponent_Result:
-			AST_LIST_LOCK(&misdn_cc_records_db);
-			cc_record = misdn_cc_find_by_invoke(bc->port, bc->fac_in.u.CCBSDeactivate.InvokeID);
-			if (cc_record) {
-				cc_record->outstanding_message = 0;
-			}
-			AST_LIST_UNLOCK(&misdn_cc_records_db);
-			break;
-
-		default:
-			chan_misdn_log(0, bc->port, " --> not yet handled: facility type:0x%04X\n",
-				bc->fac_in.Function);
-			break;
-		}
-		break;
-	case Fac_CCBSErase:
-		AST_LIST_LOCK(&misdn_cc_records_db);
-		cc_record = misdn_cc_find_by_reference(bc->port, bc->fac_in.u.CCBSErase.CCBSReference);
-		if (cc_record) {
-			misdn_cc_delete(cc_record);
-		}
-		AST_LIST_UNLOCK(&misdn_cc_records_db);
-		break;
-	case Fac_CCBSRemoteUserFree:
-		misdn_cc_handle_remote_user_free(bc->port, &bc->fac_in);
-		break;
-	case Fac_CCBSBFree:
-		misdn_cc_handle_b_free(bc->port, &bc->fac_in);
-		break;
-	case Fac_CCBSStatusRequest:
-		misdn_cc_handle_ccbs_status_request(bc->port, &bc->fac_in);
-		break;
-	case Fac_EraseCallLinkageID:
-		AST_LIST_LOCK(&misdn_cc_records_db);
-		cc_record = misdn_cc_find_by_linkage(bc->port,
-			bc->fac_in.u.EraseCallLinkageID.CallLinkageID);
-		if (cc_record && !cc_record->activation_requested) {
-			/*
-			 * The T-RETENTION timer expired before we requested
-			 * call completion activation.  Call completion is no
-			 * longer available.
-			 */
-			misdn_cc_delete(cc_record);
-		}
-		AST_LIST_UNLOCK(&misdn_cc_records_db);
-		break;
-	case Fac_CCBSStopAlerting:
-		/* We do not have anything to do for this message. */
-		break;
-	case Fac_CCBSRequest:
-	case Fac_CCNRRequest:
-		switch (bc->fac_in.u.CCBSRequest.ComponentType) {
-		case FacComponent_Result:
-			AST_LIST_LOCK(&misdn_cc_records_db);
-			cc_record = misdn_cc_find_by_invoke(bc->port, bc->fac_in.u.CCBSRequest.InvokeID);
-			if (cc_record && !cc_record->ptp) {
-				cc_record->outstanding_message = 0;
-				cc_record->activated = 1;
-				cc_record->mode.ptmp.recall_mode = bc->fac_in.u.CCBSRequest.Component.Result.RecallMode;
-				cc_record->mode.ptmp.reference_id = bc->fac_in.u.CCBSRequest.Component.Result.CCBSReference;
-			}
-			AST_LIST_UNLOCK(&misdn_cc_records_db);
-			break;
-
-		default:
-			chan_misdn_log(0, bc->port, " --> not yet handled: facility type:0x%04X\n",
-				bc->fac_in.Function);
-			break;
-		}
-		break;
-#if 0	/* We don't handle this yet */
-	case Fac_CCBSInterrogate:
-	case Fac_CCNRInterrogate:
-		/* We don't handle this yet */
-		break;
-	case Fac_StatusRequest:
-		/* We don't handle this yet */
-		break;
-#endif	/* We don't handle this yet */
-#if 0	/* We don't handle this yet */
-	case Fac_CCBS_T_Suspend:
-	case Fac_CCBS_T_Resume:
-		/* We don't handle this yet */
-		break;
-#endif	/* We don't handle this yet */
-	case Fac_CCBS_T_RemoteUserFree:
-		misdn_cc_handle_T_remote_user_free(bc);
-		break;
-	case Fac_CCBS_T_Available:
-		switch (event) {
-		case EVENT_ALERTING:
-		case EVENT_DISCONNECT:
-			/* CCBS-T/CCNR-T is available */
-			if (ch && ch->peer) {
-				int set_id = 1;
-
-				AST_LIST_LOCK(&misdn_cc_records_db);
-				if (ch->record_id == -1) {
-					cc_record = misdn_cc_new();
-				} else {
-					/*
-					 * We are doing a call-completion attempt
-					 * or the switch is sending us extra call-completion
-					 * availability indications (erroneously?).
-					 */
-					cc_record = misdn_cc_find_by_id(ch->record_id);
-					if (cc_record) {
-						if (cc_record->ptp && cc_record->mode.ptp.retention_enabled) {
-							/*
-							 * Call-completion is still activated.
-							 * The user does not have to request it again.
-							 */
-							chan_misdn_log(1, bc->port, " --> Call-completion request retention option is enabled\n");
-
-							set_id = 0;
-						} else {
-							if (cc_record->ptp && cc_record->mode.ptp.bc) {
-								/*
-								 * The network request retention option
-								 * is not on and the current call-completion
-								 * request is to be disabled.
-								 *
-								 * We should get here only if EVENT_DISCONNECT
-								 *
-								 * Close the call-completion signaling link
-								 */
-								cc_record->mode.ptp.bc->fac_out.Function = Fac_None;
-								cc_record->mode.ptp.bc->out_cause = AST_CAUSE_NORMAL_CLEARING;
-								misdn_lib_send_event(cc_record->mode.ptp.bc, EVENT_RELEASE_COMPLETE);
-							}
-
-							/*
-							 * Resetup the existing record for a possible new
-							 * call-completion request.
-							 */
-							new_record_id = misdn_cc_record_id_new();
-							if (new_record_id < 0) {
-								/* Looks like we must keep the old id anyway. */
-							} else {
-								cc_record->record_id = new_record_id;
-								ch->record_id = new_record_id;
-							}
-							cc_record->ptp = 1;
-							cc_record->port = bc->port;
-							memset(&cc_record->mode, 0, sizeof(cc_record->mode));
-							cc_record->invoke_id = ++misdn_invoke_id;
-							cc_record->activated = 0;
-							cc_record->outstanding_message = 0;
-							cc_record->activation_requested = 0;
-							cc_record->error_code = FacError_None;
-							cc_record->reject_code = FacReject_None;
-							memset(&cc_record->remote_user_free, 0, sizeof(cc_record->remote_user_free));
-							memset(&cc_record->b_free, 0, sizeof(cc_record->b_free));
-							cc_record->time_created = time(NULL);
-						}
-						cc_record = NULL;
-					} else {
-						/*
-						 * Where did the record go?  We will have to recapture
-						 * the call setup information.  Unfortunately, some
-						 * setup information may have been changed.
-						 */
-						ch->record_id = -1;
-						cc_record = misdn_cc_new();
-					}
-				}
-				if (cc_record) {
-					ch->record_id = cc_record->record_id;
-					cc_record->ptp = 1;
-					cc_record->port = bc->port;
-
-					/* Record call information for possible call-completion attempt. */
-					cc_record->redial.caller = bc->caller;
-					cc_record->redial.dialed = bc->dialed;
-					cc_record->redial.setup_bc_hlc_llc = bc->setup_bc_hlc_llc;
-					cc_record->redial.capability = bc->capability;
-					cc_record->redial.hdlc = bc->hdlc;
-				}
-				AST_LIST_UNLOCK(&misdn_cc_records_db);
-
-				/* Set MISDN_CC_RECORD_ID in original channel */
-				if (ch->record_id != -1 && set_id) {
-					snprintf(buf, sizeof(buf), "%ld", ch->record_id);
-				} else {
-					buf[0] = 0;
-				}
-				misdn_cc_set_peer_var(ch->peer, MISDN_CC_RECORD_ID, buf);
-			}
-			break;
-		default:
-			chan_misdn_log(0, bc->port,
-				" --> Expected in a DISCONNECT or ALERTING message: facility type:0x%04X\n",
-				bc->fac_in.Function);
-			break;
-		}
-		break;
-	case Fac_CCBS_T_Request:
-	case Fac_CCNR_T_Request:
-		switch (bc->fac_in.u.CCBS_T_Request.ComponentType) {
-		case FacComponent_Result:
-			AST_LIST_LOCK(&misdn_cc_records_db);
-			cc_record = misdn_cc_find_by_invoke(bc->port, bc->fac_in.u.CCBS_T_Request.InvokeID);
-			if (cc_record && cc_record->ptp) {
-				cc_record->outstanding_message = 0;
-				cc_record->activated = 1;
-				cc_record->mode.ptp.retention_enabled =
-					cc_record->mode.ptp.requested_retention
-					? bc->fac_in.u.CCBS_T_Request.Component.Result.RetentionSupported
-					? 1 : 0
-					: 0;
-			}
-			AST_LIST_UNLOCK(&misdn_cc_records_db);
-			break;
-
-		case FacComponent_Invoke:
-			/* We cannot be User-B in ptp mode. */
-		default:
-			chan_misdn_log(0, bc->port, " --> not yet handled: facility type:0x%04X\n",
-				bc->fac_in.Function);
-			break;
-		}
-		break;
-
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	case Fac_None:
-		break;
-	default:
-		chan_misdn_log(0, bc->port, " --> not yet handled: facility type:0x%04X\n",
-			bc->fac_in.Function);
-		break;
-	}
-}
-
-/*!
- * \internal
- * \brief Determine if the given dialed party matches our MSN.
- * \since 1.8
- *
- * \param port ISDN port
- * \param dialed Dialed party information of incoming call.
- *
- * \retval non-zero if MSN is valid.
- * \retval 0 if MSN invalid.
- */
-static int misdn_is_msn_valid(int port, const struct misdn_party_dialing *dialed)
-{
-	char number[sizeof(dialed->number)];
-
-	ast_copy_string(number, dialed->number, sizeof(number));
-	misdn_add_number_prefix(port, dialed->number_type, number, sizeof(number));
-	return misdn_cfg_is_msn_valid(port, number);
-}
-
-/************************************************************/
-/*  Receive Events from isdn_lib  here                     */
-/************************************************************/
-static enum event_response_e
-cb_events(enum event_e event, struct misdn_bchannel *bc, void *user_data)
-{
-#if defined(AST_MISDN_ENHANCEMENTS)
-	struct misdn_cc_record *cc_record;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	struct chan_list *held_ch;
-	struct chan_list *ch = find_chan_by_bc(bc);
-
-	if (event != EVENT_BCHAN_DATA && event != EVENT_TONE_GENERATE) {
-		int debuglevel = 1;
-
-		/*  Debug Only Non-Bchan */
-		if (event == EVENT_CLEANUP && !user_data) {
-			debuglevel = 5;
-		}
-
-		chan_misdn_log(debuglevel, bc->port,
-			"I IND :%s caller:\"%s\" <%s> dialed:%s pid:%d state:%s\n",
-			manager_isdn_get_info(event),
-			bc->caller.name,
-			bc->caller.number,
-			bc->dialed.number,
-			bc->pid,
-			ch ? misdn_get_ch_state(ch) : "none");
-		if (debuglevel == 1) {
-			misdn_lib_log_ies(bc);
-			chan_misdn_log(4, bc->port, " --> bc_state:%s\n", bc_state2str(bc->bc_state));
-		}
-	}
-
-	if (!ch) {
-		switch(event) {
-		case EVENT_SETUP:
-		case EVENT_DISCONNECT:
-		case EVENT_RELEASE:
-		case EVENT_RELEASE_COMPLETE:
-		case EVENT_PORT_ALARM:
-		case EVENT_RETRIEVE:
-		case EVENT_NEW_BC:
-		case EVENT_FACILITY:
-		case EVENT_REGISTER:
-			break;
-		case EVENT_CLEANUP:
-		case EVENT_TONE_GENERATE:
-		case EVENT_BCHAN_DATA:
-			return -1;
-		default:
-			chan_misdn_log(1, bc->port, "Chan not existing at the moment bc->l3id:%x bc:%p event:%s port:%d channel:%d\n", bc->l3_id, bc, manager_isdn_get_info(event), bc->port, bc->channel);
-			return -1;
-		}
-	} else {
-		switch (event) {
-		case EVENT_TONE_GENERATE:
-			break;
-		case EVENT_DISCONNECT:
-		case EVENT_RELEASE:
-		case EVENT_RELEASE_COMPLETE:
-		case EVENT_CLEANUP:
-		case EVENT_TIMEOUT:
-			if (!ch->ast) {
-				chan_misdn_log(3, bc->port, "ast_hangup already called, so we have no ast ptr anymore in event(%s)\n", manager_isdn_get_info(event));
-			}
-			break;
-		default:
-			if (!ch->ast || !MISDN_ASTERISK_TECH_PVT(ch->ast)) {
-				if (event != EVENT_BCHAN_DATA) {
-					ast_log(LOG_NOTICE, "No Ast or No private Pointer in Event (%d:%s)\n", event, manager_isdn_get_info(event));
-				}
-				chan_list_unref(ch, "No Ast or Ast private pointer");
-				return -1;
-			}
-			break;
-		}
-	}
-
-
-	switch (event) {
-	case EVENT_PORT_ALARM:
-		{
-			int boa = 0;
-			misdn_cfg_get(bc->port, MISDN_CFG_ALARM_BLOCK, &boa, sizeof(boa));
-			if (boa) {
-				cb_log(1, bc->port, " --> blocking\n");
-				misdn_lib_port_block(bc->port);
-			}
-		}
-		break;
-	case EVENT_BCHAN_ACTIVATED:
-		break;
-
-	case EVENT_NEW_CHANNEL:
-		update_name(ch->ast,bc->port,bc->channel);
-		break;
-
-	case EVENT_NEW_L3ID:
-		ch->l3id=bc->l3_id;
-		ch->addr=bc->addr;
-		break;
-
-	case EVENT_NEW_BC:
-		if (!ch) {
-			ch = find_hold_call(bc);
-		}
-
-		if (!ch) {
-			ast_log(LOG_WARNING, "NEW_BC without chan_list?\n");
-			break;
-		}
-
-		if (bc) {
-			ch->bc = (struct misdn_bchannel *) user_data;
-		}
-		break;
-
-	case EVENT_DTMF_TONE:
-	{
-		/*  sending INFOS as DTMF-Frames :) */
-		struct ast_frame fr;
-
-		memset(&fr, 0, sizeof(fr));
-		fr.frametype = AST_FRAME_DTMF;
-		fr.subclass.integer = bc->dtmf ;
-		fr.src = NULL;
-		fr.data.ptr = NULL;
-		fr.datalen = 0;
-		fr.samples = 0;
-		fr.mallocd = 0;
-		fr.offset = 0;
-		fr.delivery = ast_tv(0,0);
-
-		if (!ch->ignore_dtmf) {
-			chan_misdn_log(2, bc->port, " --> DTMF:%c\n", bc->dtmf);
-			ast_queue_frame(ch->ast, &fr);
-		} else {
-			chan_misdn_log(2, bc->port, " --> Ignoring DTMF:%c due to bridge flags\n", bc->dtmf);
-		}
-		break;
-	}
-	case EVENT_STATUS:
-		break;
-
-	case EVENT_INFORMATION:
-		if (ch->state != MISDN_CONNECTED) {
-			stop_indicate(ch);
-		}
-
-		if (!ch->ast) {
-			break;
-		}
-
-		if (ch->state == MISDN_WAITING4DIGS) {
-			RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup);
-			const char *pickupexten;
-
-			/*  Ok, incomplete Setup, waiting till extension exists */
-			if (ast_strlen_zero(bc->info_dad) && ! ast_strlen_zero(bc->keypad)) {
-				chan_misdn_log(1, bc->port, " --> using keypad as info\n");
-				ast_copy_string(bc->info_dad, bc->keypad, sizeof(bc->info_dad));
-			}
-
-			strncat(bc->dialed.number, bc->info_dad, sizeof(bc->dialed.number) - strlen(bc->dialed.number) - 1);
-			ast_channel_exten_set(ch->ast, bc->dialed.number);
-
-			ast_channel_lock(ch->ast);
-			pickup_cfg = ast_get_chan_features_pickup_config(ch->ast);
-			if (!pickup_cfg) {
-				ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n");
-				pickupexten = "";
-			} else {
-				pickupexten = ast_strdupa(pickup_cfg->pickupexten);
-			}
-			ast_channel_unlock(ch->ast);
-
-			/* Check for Pickup Request first */
-			if (!strcmp(ast_channel_exten(ch->ast), pickupexten)) {
-				if (ast_pickup_call(ch->ast)) {
-					hangup_chan(ch, bc);
-				} else {
-					ch->state = MISDN_CALLING_ACKNOWLEDGE;
-					hangup_chan(ch, bc);
-					ch->ast = NULL;
-					break;
-				}
-			}
-
-			if (!ast_canmatch_extension(ch->ast, ch->context, bc->dialed.number, 1, bc->caller.number)) {
-				if (ast_exists_extension(ch->ast, ch->context, "i", 1, bc->caller.number)) {
-					ast_log(LOG_WARNING,
-						"Extension '%s@%s' can never match. Jumping to 'i' extension. port:%d\n",
-						bc->dialed.number, ch->context, bc->port);
-					pbx_builtin_setvar_helper(ch->ast, "INVALID_EXTEN", bc->dialed.number);
-					ast_channel_exten_set(ch->ast, "i");
-					ch->state = MISDN_DIALING;
-					start_pbx(ch, bc, ch->ast);
-					break;
-				}
-
-				ast_log(LOG_WARNING,
-					"Extension '%s@%s' can never match. Disconnecting. port:%d\n"
-					"\tMaybe you want to add an 'i' extension to catch this case.\n",
-					bc->dialed.number, ch->context, bc->port);
-
-				if (bc->nt) {
-					hanguptone_indicate(ch);
-				}
-				ch->state = MISDN_EXTCANTMATCH;
-				bc->out_cause = AST_CAUSE_UNALLOCATED;
-
-				misdn_lib_send_event(bc, EVENT_DISCONNECT);
-				break;
-			}
-
-			if (ch->overlap_dial) {
-				ast_mutex_lock(&ch->overlap_tv_lock);
-				ch->overlap_tv = ast_tvnow();
-				ast_mutex_unlock(&ch->overlap_tv_lock);
-				if (ch->overlap_dial_task == -1) {
-					ch->overlap_dial_task =
-						misdn_tasks_add_variable(ch->overlap_dial, misdn_overlap_dial_task, ch);
-				}
-				break;
-			}
-
-			if (ast_exists_extension(ch->ast, ch->context, bc->dialed.number, 1, bc->caller.number))  {
-				ch->state = MISDN_DIALING;
-				start_pbx(ch, bc, ch->ast);
-			}
-		} else {
-			/*  sending INFOS as DTMF-Frames :) */
-			struct ast_frame fr;
-			int digits;
-
-			memset(&fr, 0, sizeof(fr));
-			fr.frametype = AST_FRAME_DTMF;
-			fr.subclass.integer = bc->info_dad[0] ;
-			fr.src = NULL;
-			fr.data.ptr = NULL;
-			fr.datalen = 0;
-			fr.samples = 0;
-			fr.mallocd = 0;
-			fr.offset = 0;
-			fr.delivery = ast_tv(0,0);
-
-			misdn_cfg_get(0, MISDN_GEN_APPEND_DIGITS2EXTEN, &digits, sizeof(digits));
-			if (ch->state != MISDN_CONNECTED) {
-				if (digits) {
-					strncat(bc->dialed.number, bc->info_dad, sizeof(bc->dialed.number) - strlen(bc->dialed.number) - 1);
-					ast_channel_exten_set(ch->ast, bc->dialed.number);
-				}
-
-				ast_queue_frame(ch->ast, &fr);
-			}
-		}
-		break;
-	case EVENT_SETUP:
-	{
-		struct ast_channel *chan;
-		int exceed;
-		int ai;
-		int im;
-		int append_msn = 0;
-		RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup);
-		const char *pickupexten;
-
-		if (ch) {
-			switch (ch->state) {
-			case MISDN_NOTHING:
-				chan_list_unref(ch, "Ignore found ch.  Is it for an outgoing call?");
-				ch = NULL;
-				break;
-			default:
-				chan_list_unref(ch, "Already have a call.");
-				chan_misdn_log(1, bc->port, " --> Ignoring Call we have already one\n");
-				return RESPONSE_IGNORE_SETUP_WITHOUT_CLOSE; /*  Ignore MSNs which are not in our List */
-			}
-		}
-
-		if (!bc->nt && !misdn_is_msn_valid(bc->port, &bc->dialed)) {
-			chan_misdn_log(1, bc->port, " --> Ignoring Call, its not in our MSN List\n");
-			return RESPONSE_IGNORE_SETUP; /*  Ignore MSNs which are not in our List */
-		}
-
-		if (bc->cw) {
-			int cause;
-			chan_misdn_log(0, bc->port, " --> Call Waiting on PMP sending RELEASE_COMPLETE\n");
-			misdn_cfg_get(bc->port, MISDN_CFG_REJECT_CAUSE, &cause, sizeof(cause));
-			bc->out_cause = cause ? cause : AST_CAUSE_NORMAL_CLEARING;
-			return RESPONSE_RELEASE_SETUP;
-		}
-
-		print_bearer(bc);
-
-		ch = chan_list_init(ORG_MISDN);
-		if (!ch) {
-			chan_misdn_log(-1, bc->port, "cb_events: malloc for chan_list failed!\n");
-			return RESPONSE_RELEASE_SETUP;
-		}
-
-		ch->bc = bc;
-		ch->l3id = bc->l3_id;
-		ch->addr = bc->addr;
-
-		{
-			struct ast_format_cap *cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
-			if (!(cap)) {
-				return RESPONSE_ERR;
-			}
-			ast_format_cap_append(cap, ast_format_alaw, 0);
-			chan = misdn_new(ch, AST_STATE_RESERVED, bc->dialed.number, bc->caller.number, cap, NULL, NULL, bc->port, bc->channel);
-			ao2_ref(cap, -1);
-		}
-		if (!chan) {
-			chan_list_unref(ch, "Failed to create a new channel");
-			ast_log(LOG_ERROR, "cb_events: misdn_new failed!\n");
-			return RESPONSE_RELEASE_SETUP;
-		}
-
-		ast_channel_lock(chan);
-		pickup_cfg = ast_get_chan_features_pickup_config(chan);
-		if (!pickup_cfg) {
-			ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n");
-			pickupexten = "";
-		} else {
-			pickupexten = ast_strdupa(pickup_cfg->pickupexten);
-		}
-		ast_channel_unlock(chan);
-
-		if ((exceed = add_in_calls(bc->port))) {
-			char tmp[16];
-			snprintf(tmp, sizeof(tmp), "%d", exceed);
-			pbx_builtin_setvar_helper(chan, "MAX_OVERFLOW", tmp);
-		}
-
-		read_config(ch);
-
-		export_ch(chan, bc, ch);
-
-		ast_channel_lock(ch->ast);
-		ast_channel_rings_set(ch->ast, 1);
-		ast_setstate(ch->ast, AST_STATE_RINGING);
-		ast_channel_unlock(ch->ast);
-
-		/* Update asterisk channel caller information */
-		chan_misdn_log(2, bc->port, " --> TON: %s(%d)\n", misdn_to_str_ton(bc->caller.number_type), bc->caller.number_type);
-		chan_misdn_log(2, bc->port, " --> PLAN: %s(%d)\n", misdn_to_str_plan(bc->caller.number_plan), bc->caller.number_plan);
-		ast_channel_caller(chan)->id.number.plan = misdn_to_ast_ton(bc->caller.number_type)
-			| misdn_to_ast_plan(bc->caller.number_plan);
-
-		chan_misdn_log(2, bc->port, " --> PRES: %s(%d)\n", misdn_to_str_pres(bc->caller.presentation), bc->caller.presentation);
-		chan_misdn_log(2, bc->port, " --> SCREEN: %s(%d)\n", misdn_to_str_screen(bc->caller.screening), bc->caller.screening);
-		ast_channel_caller(chan)->id.number.presentation = misdn_to_ast_pres(bc->caller.presentation)
-			| misdn_to_ast_screen(bc->caller.screening);
-
-		ast_set_callerid(chan, bc->caller.number, NULL, bc->caller.number);
-
-		misdn_cfg_get(bc->port, MISDN_CFG_APPEND_MSN_TO_CALLERID_TAG, &append_msn, sizeof(append_msn));
-		if (append_msn) {
-			strncat(bc->incoming_cid_tag, "_", sizeof(bc->incoming_cid_tag) - strlen(bc->incoming_cid_tag) - 1);
-			strncat(bc->incoming_cid_tag, bc->dialed.number, sizeof(bc->incoming_cid_tag) - strlen(bc->incoming_cid_tag) - 1);
-		}
-
-		ast_channel_lock(chan);
-		ast_channel_caller(chan)->id.tag = ast_strdup(bc->incoming_cid_tag);
-		ast_channel_unlock(chan);
-
-		if (!ast_strlen_zero(bc->redirecting.from.number)) {
-			/* Add configured prefix to redirecting.from.number */
-			misdn_add_number_prefix(bc->port, bc->redirecting.from.number_type, bc->redirecting.from.number, sizeof(bc->redirecting.from.number));
-
-			/* Update asterisk channel redirecting information */
-			misdn_copy_redirecting_to_ast(chan, &bc->redirecting, bc->incoming_cid_tag);
-		}
-
-		pbx_builtin_setvar_helper(chan, "TRANSFERCAPABILITY", ast_transfercapability2str(bc->capability));
-		ast_channel_transfercapability_set(chan, bc->capability);
-
-		switch (bc->capability) {
-		case INFO_CAPABILITY_DIGITAL_UNRESTRICTED:
-			pbx_builtin_setvar_helper(chan, "CALLTYPE", "DIGITAL");
-			break;
-		default:
-			pbx_builtin_setvar_helper(chan, "CALLTYPE", "SPEECH");
-			break;
-		}
-
-		if (!strstr(ch->allowed_bearers, "all")) {
-			int i;
-
-			for (i = 0; i < ARRAY_LEN(allowed_bearers_array); ++i) {
-				if (allowed_bearers_array[i].cap == bc->capability) {
-					if (strstr(ch->allowed_bearers, allowed_bearers_array[i].name)) {
-						/* The bearer capability is allowed */
-						if (allowed_bearers_array[i].deprecated) {
-							chan_misdn_log(0, bc->port, "%s in allowed_bearers list is deprecated\n",
-								allowed_bearers_array[i].name);
-						}
-						break;
-					}
-				}
-			}
-			if (i == ARRAY_LEN(allowed_bearers_array)) {
-				/* We did not find the bearer capability */
-				chan_misdn_log(0, bc->port, "Bearer capability not allowed: %s(%d)\n",
-					bearer2str(bc->capability), bc->capability);
-
-				ch->state = MISDN_EXTCANTMATCH;
-				chan_list_unref(ch, "BC not allowed, releasing call");
-				bc->out_cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
-				return RESPONSE_RELEASE_SETUP;
-			}
-		}
-
-		/** queue new chan **/
-		cl_queue_chan(ch);
-
-		if (bc->fac_in.Function != Fac_None) {
-			misdn_facility_ie_handler(event, bc, ch);
-		}
-
-		/* Check for Pickup Request first */
-		if (!strcmp(ast_channel_exten(chan), pickupexten)) {
-			if (!ch->noautorespond_on_setup) {
-				/* Sending SETUP_ACK */
-				misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE);
-			} else {
-				ch->state = MISDN_INCOMING_SETUP;
-			}
-			if (ast_pickup_call(chan)) {
-				hangup_chan(ch, bc);
-			} else {
-				ch->state = MISDN_CALLING_ACKNOWLEDGE;
-				hangup_chan(ch, bc);
-				ch->ast = NULL;
-				break;
-			}
-		}
-
-		/*
-		 * added support for s extension hope it will help those poor cretains
-		 * which haven't overlap dial.
-		 */
-		misdn_cfg_get(bc->port, MISDN_CFG_ALWAYS_IMMEDIATE, &ai, sizeof(ai));
-		if (ai) {
-			do_immediate_setup(bc, ch, chan);
-			break;
-		}
-
-		/* check if we should jump into s when we have no dialed.number */
-		misdn_cfg_get(bc->port, MISDN_CFG_IMMEDIATE, &im, sizeof(im));
-		if (im && ast_strlen_zero(bc->dialed.number)) {
-			do_immediate_setup(bc, ch, chan);
-			break;
-		}
-
-		chan_misdn_log(5, bc->port, "CONTEXT:%s\n", ch->context);
-		if (!ast_canmatch_extension(ch->ast, ch->context, bc->dialed.number, 1, bc->caller.number)) {
-			if (ast_exists_extension(ch->ast, ch->context, "i", 1, bc->caller.number)) {
-				ast_log(LOG_WARNING,
-					"Extension '%s@%s' can never match. Jumping to 'i' extension. port:%d\n",
-					bc->dialed.number, ch->context, bc->port);
-				pbx_builtin_setvar_helper(ch->ast, "INVALID_EXTEN", bc->dialed.number);
-				ast_channel_exten_set(ch->ast, "i");
-				misdn_lib_send_event(bc, EVENT_SETUP_ACKNOWLEDGE);
-				ch->state = MISDN_DIALING;
-				start_pbx(ch, bc, chan);
-				break;
-			}
-
-			ast_log(LOG_WARNING,
-				"Extension '%s@%s' can never match. Disconnecting. port:%d\n"
-				"\tMaybe you want to add an 'i' extension to catch this case.\n",
-				bc->dialed.number, ch->context, bc->port);
-			if (bc->nt) {
-				hanguptone_indicate(ch);
-			}
-
-			ch->state = MISDN_EXTCANTMATCH;
-			bc->out_cause = AST_CAUSE_UNALLOCATED;
-
-			misdn_lib_send_event(bc, bc->nt ? EVENT_RELEASE_COMPLETE : EVENT_RELEASE);
-			break;
-		}
-
-		/* Whatever happens, when sending_complete is set or we are PTMP TE, we will definitely
-		 * jump into the dialplan, when the dialed extension does not exist, the 's' extension
-		 * will be used by Asterisk automatically. */
-		if (bc->sending_complete || (!bc->nt && !misdn_lib_is_ptp(bc->port))) {
-			if (!ch->noautorespond_on_setup) {
-				ch->state=MISDN_DIALING;
-				misdn_lib_send_event(bc, EVENT_PROCEEDING);
-			} else {
-				ch->state = MISDN_INCOMING_SETUP;
-			}
-			start_pbx(ch, bc, chan);
-			break;
-		}
-
-
-		/*
-		 * When we are NT and overlapdial is set and if
-		 * the number is empty, we wait for the ISDN timeout
-		 * instead of our own timer.
-		 */
-		if (ch->overlap_dial && bc->nt && !bc->dialed.number[0]) {
-			wait_for_digits(ch, bc, chan);
-			break;
-		}
-
-		/*
-		 * If overlapdial we will definitely send a SETUP_ACKNOWLEDGE and wait for more
-		 * Infos with a Interdigit Timeout.
-		 * */
-		if (ch->overlap_dial) {
-			ast_mutex_lock(&ch->overlap_tv_lock);
-			ch->overlap_tv = ast_tvnow();
-			ast_mutex_unlock(&ch->overlap_tv_lock);
-
-			wait_for_digits(ch, bc, chan);
-			if (ch->overlap_dial_task == -1) {
-				ch->overlap_dial_task =
-					misdn_tasks_add_variable(ch->overlap_dial, misdn_overlap_dial_task, ch);
-			}
-			break;
-		}
-
-		/* If the extension does not exist and we're not TE_PTMP we wait for more digits
-		 * without interdigit timeout.
-		 * */
-		if (!ast_exists_extension(ch->ast, ch->context, bc->dialed.number, 1, bc->caller.number))  {
-			wait_for_digits(ch, bc, chan);
-			break;
-		}
-
-		/*
-		 * If the extension exists let's just jump into it.
-		 * */
-		if (ast_exists_extension(ch->ast, ch->context, bc->dialed.number, 1, bc->caller.number)) {
-			misdn_lib_send_event(bc, bc->need_more_infos ? EVENT_SETUP_ACKNOWLEDGE : EVENT_PROCEEDING);
-			ch->state = MISDN_DIALING;
-			start_pbx(ch, bc, chan);
-			break;
-		}
-		break;
-	}
-#if defined(AST_MISDN_ENHANCEMENTS)
-	case EVENT_REGISTER:
-		if (bc->fac_in.Function != Fac_None) {
-			misdn_facility_ie_handler(event, bc, ch);
-		}
-		/*
-		 * Shut down this connection immediately.
-		 * The current design of chan_misdn data structures
-		 * does not allow the proper handling of inbound call records
-		 * without an assigned B channel.  Therefore, we cannot
-		 * be the CCBS User-B party in a point-to-point setup.
-		 */
-		bc->fac_out.Function = Fac_None;
-		bc->out_cause = AST_CAUSE_NORMAL_CLEARING;
-		misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE);
-		break;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	case EVENT_SETUP_ACKNOWLEDGE:
-		ch->state = MISDN_CALLING_ACKNOWLEDGE;
-
-		if (bc->channel) {
-			update_name(ch->ast,bc->port,bc->channel);
-		}
-
-		if (bc->fac_in.Function != Fac_None) {
-			misdn_facility_ie_handler(event, bc, ch);
-		}
-
-		if (!ast_strlen_zero(bc->infos_pending)) {
-			/* TX Pending Infos */
-			strncat(bc->dialed.number, bc->infos_pending, sizeof(bc->dialed.number) - strlen(bc->dialed.number) - 1);
-
-			if (!ch->ast) {
-				break;
-			}
-			ast_channel_exten_set(ch->ast, bc->dialed.number);
-			ast_copy_string(bc->info_dad, bc->infos_pending, sizeof(bc->info_dad));
-			ast_copy_string(bc->infos_pending, "", sizeof(bc->infos_pending));
-
-			misdn_lib_send_event(bc, EVENT_INFORMATION);
-		}
-		break;
-	case EVENT_PROCEEDING:
-		if (misdn_cap_is_speech(bc->capability) &&
-			misdn_inband_avail(bc)) {
-			start_bc_tones(ch);
-		}
-
-		ch->state = MISDN_PROCEEDING;
-
-		if (bc->fac_in.Function != Fac_None) {
-			misdn_facility_ie_handler(event, bc, ch);
-		}
-
-		if (!ch->ast) {
-			break;
-		}
-
-		ast_queue_control(ch->ast, AST_CONTROL_PROCEEDING);
-		break;
-	case EVENT_PROGRESS:
-		if (bc->channel) {
-			update_name(ch->ast, bc->port, bc->channel);
-		}
-
-		if (bc->fac_in.Function != Fac_None) {
-			misdn_facility_ie_handler(event, bc, ch);
-		}
-
-		if (!bc->nt) {
-			if (misdn_cap_is_speech(bc->capability) &&
-				misdn_inband_avail(bc)) {
-				start_bc_tones(ch);
-			}
-
-			ch->state = MISDN_PROGRESS;
-
-			if (!ch->ast) {
-				break;
-			}
-			ast_queue_control(ch->ast, AST_CONTROL_PROGRESS);
-		}
-		break;
-	case EVENT_ALERTING:
-		ch->state = MISDN_ALERTING;
-
-		if (!ch->ast) {
-			break;
-		}
-
-		if (bc->fac_in.Function != Fac_None) {
-			misdn_facility_ie_handler(event, bc, ch);
-		}
-
-		ast_queue_control(ch->ast, AST_CONTROL_RINGING);
-		ast_channel_lock(ch->ast);
-		ast_setstate(ch->ast, AST_STATE_RINGING);
-		ast_channel_unlock(ch->ast);
-
-		cb_log(7, bc->port, " --> Set State Ringing\n");
-
-		if (misdn_cap_is_speech(bc->capability) && misdn_inband_avail(bc)) {
-			cb_log(1, bc->port, "Starting Tones, we have inband Data\n");
-			start_bc_tones(ch);
-		} else {
-			cb_log(3, bc->port, " --> We have no inband Data, the other end must create ringing\n");
-			if (ch->far_alerting) {
-				cb_log(1, bc->port, " --> The other end can not do ringing eh ?.. we must do all ourself..");
-				start_bc_tones(ch);
-				/*tone_indicate(ch, TONE_FAR_ALERTING);*/
-			}
-		}
-		break;
-	case EVENT_CONNECT:
-		if (bc->fac_in.Function != Fac_None) {
-			misdn_facility_ie_handler(event, bc, ch);
-		}
-#if defined(AST_MISDN_ENHANCEMENTS)
-		if (bc->div_leg_3_rx_wanted) {
-			bc->div_leg_3_rx_wanted = 0;
-
-			if (ch->ast) {
-				struct ast_party_redirecting redirecting;
-
-				ast_channel_redirecting(ch->ast)->to.number.presentation =
-					AST_PRES_RESTRICTED | AST_PRES_USER_NUMBER_UNSCREENED;
-				ast_party_redirecting_init(&redirecting);
-				ast_party_redirecting_copy(&redirecting, ast_channel_redirecting(ch->ast));
-
-				/*
-				 * Reset any earlier private redirecting id representations and
-				 * make sure that it is invalidated at the remote end.
-				 */
-				ast_party_id_reset(&redirecting.priv_orig);
-				ast_party_id_reset(&redirecting.priv_from);
-				ast_party_id_reset(&redirecting.priv_to);
-
-				ast_channel_queue_redirecting_update(ch->ast, &redirecting, NULL);
-				ast_party_redirecting_free(&redirecting);
-			}
-		}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-		/* we answer when we've got our very new L3 ID from the NT stack */
-		misdn_lib_send_event(bc, EVENT_CONNECT_ACKNOWLEDGE);
-
-		if (!ch->ast) {
-			break;
-		}
-
-		stop_indicate(ch);
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-		if (ch->record_id != -1) {
-			/*
-			 * We will delete the associated call completion
-			 * record since we now have a completed call.
-			 * We will not wait/depend on the network to tell
-			 * us to delete it.
-			 */
-			AST_LIST_LOCK(&misdn_cc_records_db);
-			cc_record = misdn_cc_find_by_id(ch->record_id);
-			if (cc_record) {
-				if (cc_record->ptp && cc_record->mode.ptp.bc) {
-					/* Close the call-completion signaling link */
-					cc_record->mode.ptp.bc->fac_out.Function = Fac_None;
-					cc_record->mode.ptp.bc->out_cause = AST_CAUSE_NORMAL_CLEARING;
-					misdn_lib_send_event(cc_record->mode.ptp.bc, EVENT_RELEASE_COMPLETE);
-				}
-				misdn_cc_delete(cc_record);
-			}
-			AST_LIST_UNLOCK(&misdn_cc_records_db);
-			ch->record_id = -1;
-			if (ch->peer) {
-				misdn_cc_set_peer_var(ch->peer, MISDN_CC_RECORD_ID, "");
-
-				ao2_ref(ch->peer, -1);
-				ch->peer = NULL;
-			}
-		}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-		if (!ast_strlen_zero(bc->connected.number)) {
-			/* Add configured prefix to connected.number */
-			misdn_add_number_prefix(bc->port, bc->connected.number_type, bc->connected.number, sizeof(bc->connected.number));
-
-			/* Update the connected line information on the other channel */
-			misdn_update_remote_party(ch->ast, &bc->connected, AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER, bc->incoming_cid_tag);
-		}
-
-		ch->l3id = bc->l3_id;
-		ch->addr = bc->addr;
-
-		start_bc_tones(ch);
-
-		ch->state = MISDN_CONNECTED;
-
-		ast_queue_control(ch->ast, AST_CONTROL_ANSWER);
-		break;
-	case EVENT_CONNECT_ACKNOWLEDGE:
-		ch->l3id = bc->l3_id;
-		ch->addr = bc->addr;
-
-		start_bc_tones(ch);
-
-		ch->state = MISDN_CONNECTED;
-		break;
-	case EVENT_DISCONNECT:
-		/* we might not have an ch->ast ptr here anymore */
-		if (ch) {
-			if (bc->fac_in.Function != Fac_None) {
-				misdn_facility_ie_handler(event, bc, ch);
-			}
-
-			chan_misdn_log(3, bc->port, " --> org:%d nt:%d, inbandavail:%d state:%d\n", ch->originator, bc->nt, misdn_inband_avail(bc), ch->state);
-			if (ch->originator == ORG_AST && !bc->nt && misdn_inband_avail(bc) && ch->state != MISDN_CONNECTED) {
-				/* If there's inband information available (e.g. a
-				   recorded message saying what was wrong with the
-				   dialled number, or perhaps even giving an
-				   alternative number, then play it instead of
-				   immediately releasing the call */
-				chan_misdn_log(1, bc->port, " --> Inband Info Avail, not sending RELEASE\n");
-
-				ch->state = MISDN_DISCONNECTED;
-				start_bc_tones(ch);
-
-				if (ch->ast) {
-					ast_channel_hangupcause_set(ch->ast, bc->cause);
-					if (bc->cause == AST_CAUSE_USER_BUSY) {
-						ast_queue_control(ch->ast, AST_CONTROL_BUSY);
-					}
-				}
-				ch->need_busy = 0;
-				break;
-			}
-
-			bc->need_disconnect = 0;
-			stop_bc_tones(ch);
-
-			/* Check for held channel, to implement transfer */
-			held_ch = find_hold_call(bc);
-			if (!held_ch || !ch->ast || misdn_attempt_transfer(ch, held_ch)) {
-				hangup_chan(ch, bc);
-			}
-		} else {
-			held_ch = find_hold_call_l3(bc->l3_id);
-			if (held_ch) {
-				if (bc->fac_in.Function != Fac_None) {
-					misdn_facility_ie_handler(event, bc, held_ch);
-				}
-
-				if (held_ch->hold.state == MISDN_HOLD_ACTIVE) {
-					bc->need_disconnect = 0;
-
-#if defined(TRANSFER_ON_HELD_CALL_HANGUP)
-					/*
-					 * Some phones disconnect the held call and the active call at the
-					 * same time to do the transfer.  Unfortunately, either call could
-					 * be disconnected first.
-					 */
-					ch = find_hold_active_call(bc);
-					if (!ch || misdn_attempt_transfer(ch, held_ch)) {
-						held_ch->hold.state = MISDN_HOLD_DISCONNECT;
-						hangup_chan(held_ch, bc);
-					}
-#else
-					hangup_chan(held_ch, bc);
-#endif	/* defined(TRANSFER_ON_HELD_CALL_HANGUP) */
-				}
-			}
-		}
-		if (held_ch) {
-			chan_list_unref(held_ch, "Done with held call");
-		}
-		bc->out_cause = -1;
-		if (bc->need_release) {
-			misdn_lib_send_event(bc, EVENT_RELEASE);
-		}
-		break;
-	case EVENT_RELEASE:
-		if (!ch) {
-			ch = find_hold_call_l3(bc->l3_id);
-			if (!ch) {
-				chan_misdn_log(1, bc->port,
-					" --> no Ch, so we've already released. (%s)\n",
-					manager_isdn_get_info(event));
-				return -1;
-			}
-		}
-		if (bc->fac_in.Function != Fac_None) {
-			misdn_facility_ie_handler(event, bc, ch);
-		}
-
-		bc->need_disconnect = 0;
-		bc->need_release = 0;
-
-		hangup_chan(ch, bc);
-		release_chan(ch, bc);
-		break;
-	case EVENT_RELEASE_COMPLETE:
-		if (!ch) {
-			ch = find_hold_call_l3(bc->l3_id);
-		}
-
-		bc->need_disconnect = 0;
-		bc->need_release = 0;
-		bc->need_release_complete = 0;
-
-		if (ch) {
-			if (bc->fac_in.Function != Fac_None) {
-				misdn_facility_ie_handler(event, bc, ch);
-			}
-
-			stop_bc_tones(ch);
-			hangup_chan(ch, bc);
-			release_chan(ch, bc);
-		} else {
-#if defined(AST_MISDN_ENHANCEMENTS)
-			/*
-			 * A call-completion signaling link established with
-			 * REGISTER does not have a struct chan_list record
-			 * associated with it.
-			 */
-			AST_LIST_LOCK(&misdn_cc_records_db);
-			cc_record = misdn_cc_find_by_bc(bc);
-			if (cc_record) {
-				/* The call-completion signaling link is closed. */
-				misdn_cc_delete(cc_record);
-			}
-			AST_LIST_UNLOCK(&misdn_cc_records_db);
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-			chan_misdn_log(1, bc->port,
-				" --> no Ch, so we've already released. (%s)\n",
-				manager_isdn_get_info(event));
-		}
-		break;
-	case EVENT_BCHAN_ERROR:
-	case EVENT_CLEANUP:
-		stop_bc_tones(ch);
-
-		switch (ch->state) {
-		case MISDN_CALLING:
-			bc->cause = AST_CAUSE_DESTINATION_OUT_OF_ORDER;
-			break;
-		default:
-			break;
-		}
-
-		hangup_chan(ch, bc);
-		release_chan(ch, bc);
-		break;
-	case EVENT_TONE_GENERATE:
-	{
-		int tone_len = bc->tone_cnt;
-		struct ast_channel *ast = ch->ast;
-		void *tmp;
-		int res;
-		int (*generate)(struct ast_channel *chan, void *tmp, int datalen, int samples);
-
-		chan_misdn_log(9, bc->port, "TONE_GEN: len:%d\n", tone_len);
-
-		if (!ast) {
-			break;
-		}
-
-		if (!ast_channel_generator(ast)) {
-			break;
-		}
-
-		tmp = ast_channel_generatordata(ast);
-		ast_channel_generatordata_set(ast, NULL);
-		generate = ast_channel_generator(ast)->generate;
-
-		if (tone_len < 0 || tone_len > 512) {
-			ast_log(LOG_NOTICE, "TONE_GEN: len was %d, set to 128\n", tone_len);
-			tone_len = 128;
-		}
-
-		res = generate(ast, tmp, tone_len, tone_len);
-		ast_channel_generatordata_set(ast, tmp);
-
-		if (res) {
-			ast_log(LOG_WARNING, "Auto-deactivating generator\n");
-			ast_deactivate_generator(ast);
-		} else {
-			bc->tone_cnt = 0;
-		}
-		break;
-	}
-	case EVENT_BCHAN_DATA:
-		if (ch->bc->AOCD_need_export) {
-			export_aoc_vars(ch->originator, ch->ast, ch->bc);
-		}
-		if (!misdn_cap_is_speech(ch->bc->capability)) {
-			struct ast_frame frame;
-
-			/* In Data Modes we queue frames */
-			memset(&frame, 0, sizeof(frame));
-			frame.frametype = AST_FRAME_VOICE; /* we have no data frames yet */
-			frame.subclass.format = ast_format_alaw;
-			frame.datalen = bc->bframe_len;
-			frame.samples = bc->bframe_len;
-			frame.mallocd = 0;
-			frame.offset = 0;
-			frame.delivery = ast_tv(0, 0);
-			frame.src = NULL;
-			frame.data.ptr = bc->bframe;
-
-			if (ch->ast) {
-				ast_queue_frame(ch->ast, &frame);
-			}
-		} else {
-			struct pollfd pfd = { .fd = ch->pipe[1], .events = POLLOUT };
-			int t;
-
-			t = ast_poll(&pfd, 1, 0);
-
-			if (t < 0) {
-				chan_misdn_log(-1, bc->port, "poll() error (err=%s)\n", strerror(errno));
-				break;
-			}
-			if (!t) {
-				chan_misdn_log(9, bc->port, "poll() timed out\n");
-				break;
-			}
-
-			if (pfd.revents & POLLOUT) {
-				chan_misdn_log(9, bc->port, "writing %d bytes to asterisk\n", bc->bframe_len);
-				if (write(ch->pipe[1], bc->bframe, bc->bframe_len) <= 0) {
-					chan_misdn_log(0, bc->port, "Write returned <=0 (err=%s) --> hanging up channel\n", strerror(errno));
-
-					stop_bc_tones(ch);
-					hangup_chan(ch, bc);
-					release_chan(ch, bc);
-				}
-			} else {
-				chan_misdn_log(1, bc->port, "Write Pipe full!\n");
-			}
-		}
-		break;
-	case EVENT_TIMEOUT:
-		if (ch && bc) {
-			chan_misdn_log(1, bc->port, "--> state: %s\n", misdn_get_ch_state(ch));
-		}
-
-		switch (ch->state) {
-		case MISDN_DIALING:
-		case MISDN_PROGRESS:
-			if (bc->nt && !ch->nttimeout) {
-				break;
-			}
-			/* fall-through */
-		case MISDN_CALLING:
-		case MISDN_ALERTING:
-		case MISDN_PROCEEDING:
-		case MISDN_CALLING_ACKNOWLEDGE:
-			if (bc->nt) {
-				bc->progress_indicator = INFO_PI_INBAND_AVAILABLE;
-				hanguptone_indicate(ch);
-			}
-
-			bc->out_cause = AST_CAUSE_UNALLOCATED;
-			misdn_lib_send_event(bc, EVENT_DISCONNECT);
-			break;
-		case MISDN_WAITING4DIGS:
-			if (bc->nt) {
-				bc->progress_indicator = INFO_PI_INBAND_AVAILABLE;
-				bc->out_cause = AST_CAUSE_UNALLOCATED;
-				hanguptone_indicate(ch);
-				misdn_lib_send_event(bc, EVENT_DISCONNECT);
-			} else {
-				bc->out_cause = AST_CAUSE_NORMAL_CLEARING;
-				misdn_lib_send_event(bc, EVENT_RELEASE);
-			}
-			break;
-		case MISDN_CLEANING:
-			chan_misdn_log(1, bc->port, " --> in state cleaning .. so ignoring, the stack should clean it for us\n");
-			break;
-		default:
-			misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE);
-			break;
-		}
-		break;
-
-	/****************************/
-	/** Supplementary Services **/
-	/****************************/
-	case EVENT_RETRIEVE:
-		if (!ch) {
-			chan_misdn_log(4, bc->port, " --> no CH, searching for held call\n");
-			ch = find_hold_call_l3(bc->l3_id);
-			if (!ch || ch->hold.state != MISDN_HOLD_ACTIVE) {
-				ast_log(LOG_WARNING, "No held call found, cannot Retrieve\n");
-				misdn_lib_send_event(bc, EVENT_RETRIEVE_REJECT);
-				break;
-			}
-		}
-
-		/* remember the channel again */
-		ch->bc = bc;
-
-		ch->hold.state = MISDN_HOLD_IDLE;
-		ch->hold.port = 0;
-		ch->hold.channel = 0;
-
-		ast_queue_unhold(ch->ast);
-
-		if (misdn_lib_send_event(bc, EVENT_RETRIEVE_ACKNOWLEDGE) < 0) {
-			chan_misdn_log(4, bc->port, " --> RETRIEVE_ACK failed\n");
-			misdn_lib_send_event(bc, EVENT_RETRIEVE_REJECT);
-		}
-		break;
-	case EVENT_HOLD:
-	{
-		int hold_allowed;
-		RAII_VAR(struct ast_channel *, bridged, NULL, ast_channel_cleanup);
-
-		misdn_cfg_get(bc->port, MISDN_CFG_HOLD_ALLOWED, &hold_allowed, sizeof(hold_allowed));
-		if (!hold_allowed) {
-			chan_misdn_log(-1, bc->port, "Hold not allowed this port.\n");
-			misdn_lib_send_event(bc, EVENT_HOLD_REJECT);
-			break;
-		}
-
-		bridged = ast_channel_bridge_peer(ch->ast);
-		if (bridged) {
-			chan_misdn_log(2, bc->port, "Bridge Partner is of type: %s\n", ast_channel_tech(bridged)->type);
-			ch->l3id = bc->l3_id;
-
-			/* forget the channel now */
-			ch->bc = NULL;
-			ch->hold.state = MISDN_HOLD_ACTIVE;
-			ch->hold.port = bc->port;
-			ch->hold.channel = bc->channel;
-
-			ast_queue_hold(ch->ast, NULL);
-
-			misdn_lib_send_event(bc, EVENT_HOLD_ACKNOWLEDGE);
-		} else {
-			misdn_lib_send_event(bc, EVENT_HOLD_REJECT);
-			chan_misdn_log(0, bc->port, "We aren't bridged to anybody\n");
-		}
-		break;
-	}
-	case EVENT_NOTIFY:
-		if (bc->redirecting.to_changed) {
-			/* Add configured prefix to redirecting.to.number */
-			misdn_add_number_prefix(bc->port, bc->redirecting.to.number_type,
-				bc->redirecting.to.number, sizeof(bc->redirecting.to.number));
-		}
-		switch (bc->notify_description_code) {
-		case mISDN_NOTIFY_CODE_DIVERSION_ACTIVATED:
-			/* Ignore for now. */
-			bc->redirecting.to_changed = 0;
-			break;
-		case mISDN_NOTIFY_CODE_CALL_IS_DIVERTING:
-		{
-			struct ast_party_redirecting redirecting;
-
-			if (!bc->redirecting.to_changed) {
-				break;
-			}
-			bc->redirecting.to_changed = 0;
-			if (!ch || !ch->ast) {
-				break;
-			}
-			switch (ch->state) {
-			case MISDN_ALERTING:
-				/* Call is deflecting after we have seen an ALERTING message */
-				bc->redirecting.reason = mISDN_REDIRECTING_REASON_NO_REPLY;
-				break;
-			default:
-				/* Call is deflecting for call forwarding unconditional or busy reason. */
-				bc->redirecting.reason = mISDN_REDIRECTING_REASON_UNKNOWN;
-				break;
-			}
-			misdn_copy_redirecting_to_ast(ch->ast, &bc->redirecting, bc->incoming_cid_tag);
-			ast_party_redirecting_init(&redirecting);
-			ast_party_redirecting_copy(&redirecting, ast_channel_redirecting(ch->ast));
-
-			/*
-			 * Reset any earlier private redirecting id representations and
-			 * make sure that it is invalidated at the remote end.
-			 */
-			ast_party_id_reset(&redirecting.priv_orig);
-			ast_party_id_reset(&redirecting.priv_from);
-			ast_party_id_reset(&redirecting.priv_to);
-
-			ast_channel_queue_redirecting_update(ch->ast, &redirecting, NULL);
-			ast_party_redirecting_free(&redirecting);
-			break;
-		}
-		case mISDN_NOTIFY_CODE_CALL_TRANSFER_ALERTING:
-			/*
-			 * It would be preferable to update the connected line information
-			 * only when the message callStatus is active.  However, the
-			 * optional redirection number may not be present in the active
-			 * message if an alerting message were received earlier.
-			 *
-			 * The consequences if we wind up sending two updates is benign.
-			 * The other end will think that it got transferred twice.
-			 */
-			if (!bc->redirecting.to_changed) {
-				break;
-			}
-			bc->redirecting.to_changed = 0;
-			if (!ch || !ch->ast) {
-				break;
-			}
-			misdn_update_remote_party(ch->ast, &bc->redirecting.to,
-				AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING,
-				bc->incoming_cid_tag);
-			break;
-		case mISDN_NOTIFY_CODE_CALL_TRANSFER_ACTIVE:
-			if (!bc->redirecting.to_changed) {
-				break;
-			}
-			bc->redirecting.to_changed = 0;
-			if (!ch || !ch->ast) {
-				break;
-			}
-			misdn_update_remote_party(ch->ast, &bc->redirecting.to,
-				AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, bc->incoming_cid_tag);
-			break;
-		default:
-			bc->redirecting.to_changed = 0;
-			chan_misdn_log(0, bc->port," --> not yet handled: notify code:0x%02X\n",
-				bc->notify_description_code);
-			break;
-		}
-		bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID;
-		break;
-	case EVENT_FACILITY:
-		if (bc->fac_in.Function == Fac_None) {
-			/* This is a FACILITY message so we MUST have a facility ie */
-			chan_misdn_log(0, bc->port," --> Missing facility ie or unknown facility ie contents.\n");
-		} else {
-			misdn_facility_ie_handler(event, bc, ch);
-		}
-
-		/* In case it came in on a FACILITY message and we did not handle it. */
-		bc->redirecting.to_changed = 0;
-		bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID;
-		break;
-	case EVENT_RESTART:
-		if (!bc->dummy) {
-			stop_bc_tones(ch);
-			release_chan(ch, bc);
-		}
-		break;
-	default:
-		chan_misdn_log(1, 0, "Got Unknown Event\n");
-		break;
-	}
-
-	if (ch) {
-		chan_list_unref(ch, "cb_event complete OK");
-	}
-	return RESPONSE_OK;
-}
-
-/** TE STUFF END **/
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Get call completion record information.
- *
- * \param chan Asterisk channel to operate upon. (Not used)
- * \param function_name Name of the function that called us.
- * \param function_args Argument string passed to function (Could be NULL)
- * \param buf Buffer to put returned string.
- * \param size Size of the supplied buffer including the null terminator.
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int misdn_cc_read(struct ast_channel *chan, const char *function_name,
-	char *function_args, char *buf, size_t size)
-{
-	char *parse;
-	struct misdn_cc_record *cc_record;
-
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(cc_id);		/* Call completion record ID value. */
-		AST_APP_ARG(get_name);	/* Name of what to get */
-		AST_APP_ARG(other);		/* Any extraneous garbage arguments */
-	);
-
-	/* Ensure that the buffer is empty */
-	*buf = 0;
-
-	if (ast_strlen_zero(function_args)) {
-		ast_log(LOG_ERROR, "Function '%s' requires arguments.\n", function_name);
-		return -1;
-	}
-
-	parse = ast_strdupa(function_args);
-	AST_STANDARD_APP_ARGS(args, parse);
-
-	if (!args.argc || ast_strlen_zero(args.cc_id)) {
-		ast_log(LOG_ERROR, "Function '%s' missing call completion record ID.\n",
-			function_name);
-		return -1;
-	}
-	if (!isdigit(*args.cc_id)) {
-		ast_log(LOG_ERROR, "Function '%s' call completion record ID must be numeric.\n",
-			function_name);
-		return -1;
-	}
-
-	if (ast_strlen_zero(args.get_name)) {
-		ast_log(LOG_ERROR, "Function '%s' missing what-to-get parameter.\n",
-			function_name);
-		return -1;
-	}
-
-	AST_LIST_LOCK(&misdn_cc_records_db);
-	cc_record = misdn_cc_find_by_id(atoi(args.cc_id));
-	if (cc_record) {
-		if (!strcasecmp("a-all", args.get_name)) {
-			snprintf(buf, size, "\"%s\" <%s>", cc_record->redial.caller.name,
-				cc_record->redial.caller.number);
-		} else if (!strcasecmp("a-name", args.get_name)) {
-			ast_copy_string(buf, cc_record->redial.caller.name, size);
-		} else if (!strncasecmp("a-num", args.get_name, 5)) {
-			ast_copy_string(buf, cc_record->redial.caller.number, size);
-		} else if (!strcasecmp("a-ton", args.get_name)) {
-			snprintf(buf, size, "%d",
-				misdn_to_ast_plan(cc_record->redial.caller.number_plan)
-				| misdn_to_ast_ton(cc_record->redial.caller.number_type));
-		} else if (!strncasecmp("a-pres", args.get_name, 6)) {
-			ast_copy_string(buf, ast_named_caller_presentation(
-				misdn_to_ast_pres(cc_record->redial.caller.presentation)
-				| misdn_to_ast_screen(cc_record->redial.caller.screening)), size);
-		} else if (!strcasecmp("a-busy", args.get_name)) {
-			ast_copy_string(buf, cc_record->party_a_free ? "no" : "yes", size);
-		} else if (!strncasecmp("b-num", args.get_name, 5)) {
-			ast_copy_string(buf, cc_record->redial.dialed.number, size);
-		} else if (!strcasecmp("b-ton", args.get_name)) {
-			snprintf(buf, size, "%d",
-				misdn_to_ast_plan(cc_record->redial.dialed.number_plan)
-				| misdn_to_ast_ton(cc_record->redial.dialed.number_type));
-		} else if (!strcasecmp("port", args.get_name)) {
-			snprintf(buf, size, "%d", cc_record->port);
-		} else if (!strcasecmp("available-notify-priority", args.get_name)) {
-			snprintf(buf, size, "%d", cc_record->remote_user_free.priority);
-		} else if (!strcasecmp("available-notify-exten", args.get_name)) {
-			ast_copy_string(buf, cc_record->remote_user_free.exten, size);
-		} else if (!strcasecmp("available-notify-context", args.get_name)) {
-			ast_copy_string(buf, cc_record->remote_user_free.context, size);
-		} else if (!strcasecmp("busy-notify-priority", args.get_name)) {
-			snprintf(buf, size, "%d", cc_record->b_free.priority);
-		} else if (!strcasecmp("busy-notify-exten", args.get_name)) {
-			ast_copy_string(buf, cc_record->b_free.exten, size);
-		} else if (!strcasecmp("busy-notify-context", args.get_name)) {
-			ast_copy_string(buf, cc_record->b_free.context, size);
-		} else {
-			AST_LIST_UNLOCK(&misdn_cc_records_db);
-			ast_log(LOG_ERROR, "Function '%s': Unknown what-to-get '%s'.\n", function_name, args.get_name);
-			return -1;
-		}
-	}
-	AST_LIST_UNLOCK(&misdn_cc_records_db);
-
-	return 0;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static struct ast_custom_function misdn_cc_function = {
-	.name = "mISDN_CC",
-	.synopsis = "Get call completion record information.",
-	.syntax = "mISDN_CC(${MISDN_CC_RECORD_ID},<what-to-get>)",
-	.desc =
-		"mISDN_CC(${MISDN_CC_RECORD_ID},<what-to-get>)\n"
-		"The following can be retrieved:\n"
-		"\"a-num\", \"a-name\", \"a-all\", \"a-ton\", \"a-pres\", \"a-busy\",\n"
-		"\"b-num\", \"b-ton\", \"port\",\n"
-		"  User-A is available for call completion:\n"
-		"    \"available-notify-priority\",\n"
-		"    \"available-notify-exten\",\n"
-		"    \"available-notify-context\",\n"
-		"  User-A is busy:\n"
-		"    \"busy-notify-priority\",\n"
-		"    \"busy-notify-exten\",\n"
-		"    \"busy-notify-context\"\n",
-	.read = misdn_cc_read,
-};
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-/******************************************
- *
- *   Asterisk Channel Endpoint END
- *
- *
- *******************************************/
-
-
-
-static int unload_module(void)
-{
-	/* First, take us out of the channel loop */
-	ast_verb(0, "-- Unregistering mISDN Channel Driver --\n");
-
-	misdn_tasks_destroy();
-
-	if (!g_config_initialized) {
-		return 0;
-	}
-
-	ast_cli_unregister_multiple(chan_misdn_clis, sizeof(chan_misdn_clis) / sizeof(struct ast_cli_entry));
-
-	/* ast_unregister_application("misdn_crypt"); */
-	ast_unregister_application("misdn_set_opt");
-	ast_unregister_application("misdn_facility");
-	ast_unregister_application("misdn_check_l2l1");
-#if defined(AST_MISDN_ENHANCEMENTS)
-	ast_unregister_application(misdn_command_name);
-	ast_custom_function_unregister(&misdn_cc_function);
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-	ast_channel_unregister(&misdn_tech);
-
-	free_robin_list();
-	misdn_cfg_destroy();
-	misdn_lib_destroy();
-
-	ast_free(misdn_out_calls);
-	ast_free(misdn_in_calls);
-	ast_free(misdn_debug_only);
-	ast_free(misdn_ports);
-	ast_free(misdn_debug);
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	misdn_cc_destroy();
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	ao2_cleanup(misdn_tech.capabilities);
-	misdn_tech.capabilities = NULL;
-
-	return 0;
-}
-
-/*!
- * \brief Load the module
- *
- * Module loading including tests for configuration or dependencies.
- * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
- * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
- * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the
- * configuration file or other non-critical problem return
- * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
- */
-static int load_module(void)
-{
-	int i, port;
-	int ntflags = 0, ntkc = 0;
-	char ports[256] = "";
-	char tempbuf[BUFFERSIZE + 1];
-	char ntfile[BUFFERSIZE + 1];
-	struct misdn_lib_iface iface = {
-		.cb_event = cb_events,
-		.cb_log = chan_misdn_log,
-		.cb_jb_empty = chan_misdn_jb_empty,
-	};
-
-
-	if (!(misdn_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	ast_format_cap_append(misdn_tech.capabilities, ast_format_alaw, 0);
-
-	max_ports = misdn_lib_maxports_get();
-
-	if (max_ports <= 0) {
-		ast_log(LOG_ERROR, "Unable to initialize mISDN\n");
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	if (misdn_cfg_init(max_ports, 0)) {
-		ast_log(LOG_ERROR, "Unable to initialize misdn_config.\n");
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	g_config_initialized = 1;
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	misdn_cc_init();
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-	misdn_debug = ast_malloc(sizeof(int) * (max_ports + 1));
-	if (!misdn_debug) {
-		ast_log(LOG_ERROR, "Out of memory for misdn_debug\n");
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	misdn_ports = ast_malloc(sizeof(int) * (max_ports + 1));
-	if (!misdn_ports) {
-		ast_free(misdn_debug);
-		ast_log(LOG_ERROR, "Out of memory for misdn_ports\n");
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	misdn_cfg_get(0, MISDN_GEN_DEBUG, &misdn_debug[0], sizeof(misdn_debug[0]));
-	for (i = 1; i <= max_ports; i++) {
-		misdn_debug[i] = misdn_debug[0];
-		misdn_ports[i] = i;
-	}
-	*misdn_ports = 0;
-	misdn_debug_only = ast_calloc(max_ports + 1, sizeof(int));
-	if (!misdn_debug_only) {
-		ast_free(misdn_ports);
-		ast_free(misdn_debug);
-		ast_log(LOG_ERROR, "Out of memory for misdn_debug_only\n");
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	misdn_cfg_get(0, MISDN_GEN_TRACEFILE, tempbuf, sizeof(tempbuf));
-	if (!ast_strlen_zero(tempbuf)) {
-		tracing = 1;
-	}
-
-	misdn_in_calls = ast_malloc(sizeof(int) * (max_ports + 1));
-	if (!misdn_in_calls) {
-		ast_free(misdn_debug_only);
-		ast_free(misdn_ports);
-		ast_free(misdn_debug);
-		ast_log(LOG_ERROR, "Out of memory for misdn_in_calls\n");
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	misdn_out_calls = ast_malloc(sizeof(int) * (max_ports + 1));
-	if (!misdn_out_calls) {
-		ast_free(misdn_in_calls);
-		ast_free(misdn_debug_only);
-		ast_free(misdn_ports);
-		ast_free(misdn_debug);
-		ast_log(LOG_ERROR, "Out of memory for misdn_out_calls\n");
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	for (i = 1; i <= max_ports; i++) {
-		misdn_in_calls[i] = 0;
-		misdn_out_calls[i] = 0;
-	}
-
-	ast_mutex_init(&cl_te_lock);
-	ast_mutex_init(&release_lock);
-
-	misdn_cfg_update_ptp();
-	misdn_cfg_get_ports_string(ports);
-
-	if (!ast_strlen_zero(ports)) {
-		chan_misdn_log(0, 0, "Got: %s from get_ports\n", ports);
-	}
-	if (misdn_lib_init(ports, &iface, NULL)) {
-		chan_misdn_log(0, 0, "No te ports initialized\n");
-	}
-
-	misdn_cfg_get(0, MISDN_GEN_NTDEBUGFLAGS, &ntflags, sizeof(ntflags));
-	misdn_cfg_get(0, MISDN_GEN_NTDEBUGFILE, &ntfile, sizeof(ntfile));
-	misdn_cfg_get(0, MISDN_GEN_NTKEEPCALLS, &ntkc, sizeof(ntkc));
-
-	misdn_lib_nt_keepcalls(ntkc);
-	misdn_lib_nt_debug_init(ntflags, ntfile);
-
-	if (ast_channel_register(&misdn_tech)) {
-		ast_log(LOG_ERROR, "Unable to register channel class %s\n", misdn_type);
-		unload_module();
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	ast_cli_register_multiple(chan_misdn_clis, sizeof(chan_misdn_clis) / sizeof(struct ast_cli_entry));
-
-	ast_register_application("misdn_set_opt", misdn_set_opt_exec, "misdn_set_opt",
-		"misdn_set_opt(:<opt><optarg>:<opt><optarg>...):\n"
-		"Sets mISDN opts. and optargs\n"
-		"\n"
-		"The available options are:\n"
-		"    a - Have Asterisk detect DTMF tones on called channel\n"
-		"    c - Make crypted outgoing call, optarg is keyindex\n"
-		"    d - Send display text to called phone, text is the optarg\n"
-		"    e - Perform echo cancellation on this channel,\n"
-		"        takes taps as optarg (32,64,128,256)\n"
-		"   e! - Disable echo cancellation on this channel\n"
-		"    f - Enable fax detection\n"
-		"    h - Make digital outgoing call\n"
-		"   h1 - Make HDLC mode digital outgoing call\n"
-		"    i - Ignore detected DTMF tones, don't signal them to Asterisk,\n"
-		"        they will be transported inband.\n"
-		"   jb - Set jitter buffer length, optarg is length\n"
-		"   jt - Set jitter buffer upper threshold, optarg is threshold\n"
-		"   jn - Disable jitter buffer\n"
-		"    n - Disable mISDN DSP on channel.\n"
-		"        Disables: echo cancel, DTMF detection, and volume control.\n"
-		"    p - Caller ID presentation,\n"
-		"        optarg is either 'allowed' or 'restricted'\n"
-		"    s - Send Non-inband DTMF as inband\n"
-		"   vr - Rx gain control, optarg is gain\n"
-		"   vt - Tx gain control, optarg is gain\n"
-		);
-
-
-	ast_register_application("misdn_facility", misdn_facility_exec, "misdn_facility",
-		"misdn_facility(<FACILITY_TYPE>|<ARG1>|..)\n"
-		"Sends the Facility Message FACILITY_TYPE with \n"
-		"the given Arguments to the current ISDN Channel\n"
-		"Supported Facilities are:\n"
-		"\n"
-		"type=calldeflect args=Nr where to deflect\n"
-#if defined(AST_MISDN_ENHANCEMENTS)
-		"type=callrerouting args=Nr where to deflect\n"
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-		);
-
-
-	ast_register_application("misdn_check_l2l1", misdn_check_l2l1, "misdn_check_l2l1",
-		"misdn_check_l2l1(<port>||g:<groupname>,timeout)\n"
-		"Checks if the L2 and L1 are up on either the given <port> or\n"
-		"on the ports in the group with <groupname>\n"
-		"If the L1/L2 are down, check_l2l1 gets up the L1/L2 and waits\n"
-		"for <timeout> seconds that this happens. Otherwise, nothing happens\n"
-		"\n"
-		"This application, ensures the L1/L2 state of the Ports in a group\n"
-		"it is intended to make the pmp_l1_check option redundant and to\n"
-		"fix a buggy switch config from your provider\n"
-		"\n"
-		"a sample dialplan would look like:\n\n"
-		"exten => _X.,1,misdn_check_l2l1(g:out|2)\n"
-		"exten => _X.,n,dial(mISDN/g:out/${EXTEN})\n"
-		);
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	ast_register_application(misdn_command_name, misdn_command_exec, misdn_command_name,
-		"misdn_command(<command>[,<options>])\n"
-		"The following commands are defined:\n"
-		"cc-initialize\n"
-		"  Setup mISDN support for call completion\n"
-		"  Must call before doing any Dial() involving call completion.\n"
-		"ccnr-request,${MISDN_CC_RECORD_ID},<notify-context>,<user-a-extension>,<priority>\n"
-		"  Request Call Completion No Reply activation\n"
-		"ccbs-request,${MISDN_CC_RECORD_ID},<notify-context>,<user-a-extension>,<priority>\n"
-		"  Request Call Completion Busy Subscriber activation\n"
-		"cc-b-free,${MISDN_CC_RECORD_ID},<notify-context>,<user-a-extension>,<priority>\n"
-		"  Set the dialplan location to notify when User-B is available but User-A is busy.\n"
-		"  Setting this dialplan location is optional.\n"
-		"cc-a-busy,${MISDN_CC_RECORD_ID},<yes/no>\n"
-		"  Set the busy status of call completion User-A\n"
-		"cc-deactivate,${MISDN_CC_RECORD_ID}\n"
-		"  Deactivate the identified call completion request\n"
-		"\n"
-		"MISDN_CC_RECORD_ID is set when Dial() returns and call completion is possible\n"
-		"MISDN_CC_STATUS is set to ACTIVATED or ERROR after the call completion\n"
-		"activation request.\n"
-		"MISDN_ERROR_MSG is set to a descriptive message on error.\n"
-		);
-
-	ast_custom_function_register(&misdn_cc_function);
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-	misdn_cfg_get(0, MISDN_GEN_TRACEFILE, global_tracefile, sizeof(global_tracefile));
-
-	/* start the l1 watchers */
-
-	for (port = misdn_cfg_get_next_port(0); port >= 0; port = misdn_cfg_get_next_port(port)) {
-		int l1timeout;
-		misdn_cfg_get(port, MISDN_CFG_L1_TIMEOUT, &l1timeout, sizeof(l1timeout));
-		if (l1timeout) {
-			chan_misdn_log(4, 0, "Adding L1watcher task: port:%d timeout:%ds\n", port, l1timeout);
-			misdn_tasks_add(l1timeout * 1000, misdn_l1_task, &misdn_ports[port]);
-		}
-	}
-
-	chan_misdn_log(0, 0, "-- mISDN Channel Driver Registered --\n");
-
-	return 0;
-}
-
-
-
-static int reload(void)
-{
-	reload_config();
-
-	return 0;
-}
-
-/*** SOME APPS ;)***/
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
-* \brief misdn_command arguments container.
-*/
-AST_DEFINE_APP_ARGS_TYPE(misdn_command_args,
-	AST_APP_ARG(name);			/* Subcommand name */
-	AST_APP_ARG(arg)[10 + 1];	/* Subcommand arguments */
-);
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static void misdn_cc_caller_destroy(void *obj)
-{
-	/* oh snap! */
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-static struct misdn_cc_caller *misdn_cc_caller_alloc(struct ast_channel *chan)
-{
-	struct misdn_cc_caller *cc_caller;
-
-	if (!(cc_caller = ao2_alloc(sizeof(*cc_caller), misdn_cc_caller_destroy))) {
-		return NULL;
-	}
-
-	cc_caller->chan = chan;
-
-	return cc_caller;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief misdn_command(cc-initialize) subcommand handler
- *
- * \param chan Asterisk channel to operate upon.
- * \param subcommand Arguments for the subcommand
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int misdn_command_cc_initialize(struct ast_channel *chan, struct misdn_command_args *subcommand)
-{
-	struct misdn_cc_caller *cc_caller;
-	struct ast_datastore *datastore;
-
-	if (!(cc_caller = misdn_cc_caller_alloc(chan))) {
-		return -1;
-	}
-
-	if (!(datastore = ast_datastore_alloc(&misdn_cc_ds_info, NULL))) {
-		ao2_ref(cc_caller, -1);
-		return -1;
-	}
-
-	ast_channel_lock(chan);
-
-	/* Inherit reference */
-	datastore->data = cc_caller;
-	cc_caller = NULL;
-
-	datastore->inheritance = DATASTORE_INHERIT_FOREVER;
-
-	ast_channel_datastore_add(chan, datastore);
-
-	ast_channel_unlock(chan);
-
-	return 0;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief misdn_command(cc-deactivate) subcommand handler
- *
- * \details
- * misdn_command(cc-deactivate,${MISDN_CC_RECORD_ID})
- * Deactivate a call completion service instance.
- *
- * \param chan Asterisk channel to operate upon.
- * \param subcommand Arguments for the subcommand
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int misdn_command_cc_deactivate(struct ast_channel *chan, struct misdn_command_args *subcommand)
-{
-	long record_id;
-	const char *error_str;
-	struct misdn_cc_record *cc_record;
-	struct misdn_bchannel *bc;
-	struct misdn_bchannel dummy;
-
-	static const char cmd_help[] = "%s(%s,${MISDN_CC_RECORD_ID})\n";
-
-	if (ast_strlen_zero(subcommand->arg[0]) || !isdigit(*subcommand->arg[0])) {
-		ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name);
-		return -1;
-	}
-	record_id = atol(subcommand->arg[0]);
-
-	AST_LIST_LOCK(&misdn_cc_records_db);
-	cc_record = misdn_cc_find_by_id(record_id);
-	if (cc_record && 0 <= cc_record->port) {
-		if (cc_record->ptp) {
-			if (cc_record->mode.ptp.bc) {
-				/* Close the call-completion signaling link */
-				bc = cc_record->mode.ptp.bc;
-				bc->fac_out.Function = Fac_None;
-				bc->out_cause = AST_CAUSE_NORMAL_CLEARING;
-				misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE);
-			}
-			misdn_cc_delete(cc_record);
-		} else if (cc_record->activated) {
-			cc_record->error_code = FacError_None;
-			cc_record->reject_code = FacReject_None;
-			cc_record->invoke_id = ++misdn_invoke_id;
-			cc_record->outstanding_message = 1;
-
-			/* Build message */
-			misdn_make_dummy(&dummy, cc_record->port, 0, misdn_lib_port_is_nt(cc_record->port), 0);
-			dummy.fac_out.Function = Fac_CCBSDeactivate;
-			dummy.fac_out.u.CCBSDeactivate.InvokeID = cc_record->invoke_id;
-			dummy.fac_out.u.CCBSDeactivate.ComponentType = FacComponent_Invoke;
-			dummy.fac_out.u.CCBSDeactivate.Component.Invoke.CCBSReference = cc_record->mode.ptmp.reference_id;
-
-			/* Send message */
-			print_facility(&dummy.fac_out, &dummy);
-			misdn_lib_send_event(&dummy, EVENT_FACILITY);
-		}
-	}
-	AST_LIST_UNLOCK(&misdn_cc_records_db);
-
-	/* Wait for the response to the call completion deactivation request. */
-	misdn_cc_response_wait(chan, MISDN_CC_REQUEST_WAIT_MAX, record_id);
-
-	AST_LIST_LOCK(&misdn_cc_records_db);
-	cc_record = misdn_cc_find_by_id(record_id);
-	if (cc_record) {
-		if (cc_record->port < 0) {
-			/* The network did not tell us that call completion was available. */
-			error_str = NULL;
-		} else if (cc_record->outstanding_message) {
-			cc_record->outstanding_message = 0;
-			error_str = misdn_no_response_from_network;
-		} else if (cc_record->reject_code != FacReject_None) {
-			error_str = misdn_to_str_reject_code(cc_record->reject_code);
-		} else if (cc_record->error_code != FacError_None) {
-			error_str = misdn_to_str_error_code(cc_record->error_code);
-		} else {
-			error_str = NULL;
-		}
-
-		misdn_cc_delete(cc_record);
-	} else {
-		error_str = NULL;
-	}
-	AST_LIST_UNLOCK(&misdn_cc_records_db);
-	if (error_str) {
-		ast_verb(1, "%s(%s) diagnostic '%s' on channel %s\n",
-			misdn_command_name, subcommand->name, error_str, ast_channel_name(chan));
-		pbx_builtin_setvar_helper(chan, MISDN_ERROR_MSG, error_str);
-	}
-
-	return 0;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief misdn_command(cc-a-busy) subcommand handler
- *
- * \details
- * misdn_command(cc-a-busy,${MISDN_CC_RECORD_ID},<yes/no>)
- * Set the status of User-A for a call completion service instance.
- *
- * \param chan Asterisk channel to operate upon.
- * \param subcommand Arguments for the subcommand
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int misdn_command_cc_a_busy(struct ast_channel *chan, struct misdn_command_args *subcommand)
-{
-	long record_id;
-	int party_a_free;
-	struct misdn_cc_record *cc_record;
-	struct misdn_bchannel *bc;
-
-	static const char cmd_help[] = "%s(%s,${MISDN_CC_RECORD_ID},<yes/no>)\n";
-
-	if (ast_strlen_zero(subcommand->arg[0]) || !isdigit(*subcommand->arg[0])) {
-		ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name);
-		return -1;
-	}
-	record_id = atol(subcommand->arg[0]);
-
-	if (ast_true(subcommand->arg[1])) {
-		party_a_free = 0;
-	} else if (ast_false(subcommand->arg[1])) {
-		party_a_free = 1;
-	} else {
-		ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name);
-		return -1;
-	}
-
-	AST_LIST_LOCK(&misdn_cc_records_db);
-	cc_record = misdn_cc_find_by_id(record_id);
-	if (cc_record && cc_record->party_a_free != party_a_free) {
-		/* User-A's status has changed */
-		cc_record->party_a_free = party_a_free;
-
-		if (cc_record->ptp && cc_record->mode.ptp.bc) {
-			cc_record->error_code = FacError_None;
-			cc_record->reject_code = FacReject_None;
-
-			/* Build message */
-			bc = cc_record->mode.ptp.bc;
-			if (cc_record->party_a_free) {
-				bc->fac_out.Function = Fac_CCBS_T_Resume;
-				bc->fac_out.u.CCBS_T_Resume.InvokeID = ++misdn_invoke_id;
-			} else {
-				bc->fac_out.Function = Fac_CCBS_T_Suspend;
-				bc->fac_out.u.CCBS_T_Suspend.InvokeID = ++misdn_invoke_id;
-			}
-
-			/* Send message */
-			print_facility(&bc->fac_out, bc);
-			misdn_lib_send_event(bc, EVENT_FACILITY);
-		}
-	}
-	AST_LIST_UNLOCK(&misdn_cc_records_db);
-
-	return 0;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief misdn_command(cc-b-free) subcommand handler
- *
- * \verbatim
-   misdn_command(cc-b-free,${MISDN_CC_RECORD_ID},<notify-context>,<user-a-extension>,<priority>)
-   \endverbatim
- * Set the dialplan location to notify when User-B is free and User-A is busy.
- *
- * \param chan Asterisk channel to operate upon.
- * \param subcommand Arguments for the subcommand
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int misdn_command_cc_b_free(struct ast_channel *chan, struct misdn_command_args *subcommand)
-{
-	unsigned index;
-	long record_id;
-	int priority;
-	char *context;
-	char *exten;
-	struct misdn_cc_record *cc_record;
-
-	static const char cmd_help[] = "%s(%s,${MISDN_CC_RECORD_ID},<notify-context>,<user-a-extension>,<priority>)\n";
-
-	/* Check that all arguments are present */
-	for (index = 0; index < 4; ++index) {
-		if (ast_strlen_zero(subcommand->arg[index])) {
-			ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name);
-			return -1;
-		}
-	}
-
-	/* These must be numeric */
-	if (!isdigit(*subcommand->arg[0]) || !isdigit(*subcommand->arg[3])) {
-		ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name);
-		return -1;
-	}
-
-	record_id = atol(subcommand->arg[0]);
-	context = subcommand->arg[1];
-	exten = subcommand->arg[2];
-	priority = atoi(subcommand->arg[3]);
-
-	AST_LIST_LOCK(&misdn_cc_records_db);
-	cc_record = misdn_cc_find_by_id(record_id);
-	if (cc_record) {
-		/* Save User-B free information */
-		ast_copy_string(cc_record->b_free.context, context, sizeof(cc_record->b_free.context));
-		ast_copy_string(cc_record->b_free.exten, exten, sizeof(cc_record->b_free.exten));
-		cc_record->b_free.priority = priority;
-	}
-	AST_LIST_UNLOCK(&misdn_cc_records_db);
-
-	return 0;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-struct misdn_cc_request {
-	enum FacFunction ptmp;
-	enum FacFunction ptp;
-};
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief misdn_command(ccbs-request/ccnr-request) subcommand handler helper
- *
- * \verbatim
-   misdn_command(ccbs-request,${MISDN_CC_RECORD_ID},<notify-context>,<user-a-extension>,<priority>)
-   misdn_command(ccnr-request,${MISDN_CC_RECORD_ID},<notify-context>,<user-a-extension>,<priority>)
-   \endverbatim
- * Set the dialplan location to notify when User-B is free and User-A is free.
- *
- * \param chan Asterisk channel to operate upon.
- * \param subcommand Arguments for the subcommand
- * \param request Which call-completion request message to generate.
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int misdn_command_cc_request(struct ast_channel *chan, struct misdn_command_args *subcommand, const struct misdn_cc_request *request)
-{
-	unsigned index;
-	int request_retention;
-	long record_id;
-	int priority;
-	char *context;
-	char *exten;
-	const char *error_str;
-	struct misdn_cc_record *cc_record;
-	struct misdn_bchannel *bc;
-	struct misdn_bchannel dummy;
-	struct misdn_party_id id;
-
-	static const char cmd_help[] = "%s(%s,${MISDN_CC_RECORD_ID},<notify-context>,<user-a-extension>,<priority>)\n";
-
-	/* Check that all arguments are present */
-	for (index = 0; index < 4; ++index) {
-		if (ast_strlen_zero(subcommand->arg[index])) {
-			ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name);
-			return -1;
-		}
-	}
-
-	/* These must be numeric */
-	if (!isdigit(*subcommand->arg[0]) || !isdigit(*subcommand->arg[3])) {
-		ast_log(LOG_WARNING, cmd_help, misdn_command_name, subcommand->name);
-		return -1;
-	}
-
-	record_id = atol(subcommand->arg[0]);
-	context = subcommand->arg[1];
-	exten = subcommand->arg[2];
-	priority = atoi(subcommand->arg[3]);
-
-	AST_LIST_LOCK(&misdn_cc_records_db);
-	cc_record = misdn_cc_find_by_id(record_id);
-	if (cc_record) {
-		/* Save User-B free information */
-		ast_copy_string(cc_record->remote_user_free.context, context,
-			sizeof(cc_record->remote_user_free.context));
-		ast_copy_string(cc_record->remote_user_free.exten, exten,
-			sizeof(cc_record->remote_user_free.exten));
-		cc_record->remote_user_free.priority = priority;
-
-		if (0 <= cc_record->port) {
-			if (cc_record->ptp) {
-				if (!cc_record->mode.ptp.bc) {
-					bc = misdn_lib_get_register_bc(cc_record->port);
-					if (bc) {
-						cc_record->mode.ptp.bc = bc;
-						cc_record->error_code = FacError_None;
-						cc_record->reject_code = FacReject_None;
-						cc_record->invoke_id = ++misdn_invoke_id;
-						cc_record->outstanding_message = 1;
-						cc_record->activation_requested = 1;
-
-						misdn_cfg_get(bc->port, MISDN_CFG_CC_REQUEST_RETENTION,
-							&request_retention, sizeof(request_retention));
-						cc_record->mode.ptp.requested_retention = request_retention ? 1 : 0;
-
-						/* Build message */
-						bc->fac_out.Function = request->ptp;
-						bc->fac_out.u.CCBS_T_Request.InvokeID = cc_record->invoke_id;
-						bc->fac_out.u.CCBS_T_Request.ComponentType = FacComponent_Invoke;
-						bc->fac_out.u.CCBS_T_Request.Component.Invoke.Q931ie =
-							cc_record->redial.setup_bc_hlc_llc;
-						memset(&id, 0, sizeof(id));
-						id.number_plan = cc_record->redial.dialed.number_plan;
-						id.number_type = cc_record->redial.dialed.number_type;
-						ast_copy_string(id.number, cc_record->redial.dialed.number,
-							sizeof(id.number));
-						misdn_Address_fill(
-							&bc->fac_out.u.CCBS_T_Request.Component.Invoke.Destination,
-							&id);
-						misdn_Address_fill(
-							&bc->fac_out.u.CCBS_T_Request.Component.Invoke.Originating,
-							&cc_record->redial.caller);
-						bc->fac_out.u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicatorPresent = 1;
-						bc->fac_out.u.CCBS_T_Request.Component.Invoke.PresentationAllowedIndicator =
-							(cc_record->redial.caller.presentation != 0) ? 0 : 1;
-						bc->fac_out.u.CCBS_T_Request.Component.Invoke.RetentionSupported =
-							request_retention ? 1 : 0;
-
-						/* Send message */
-						print_facility(&bc->fac_out, bc);
-						misdn_lib_send_event(bc, EVENT_REGISTER);
-					}
-				}
-			} else {
-				cc_record->error_code = FacError_None;
-				cc_record->reject_code = FacReject_None;
-				cc_record->invoke_id = ++misdn_invoke_id;
-				cc_record->outstanding_message = 1;
-				cc_record->activation_requested = 1;
-
-				/* Build message */
-				misdn_make_dummy(&dummy, cc_record->port, 0,
-					misdn_lib_port_is_nt(cc_record->port), 0);
-				dummy.fac_out.Function = request->ptmp;
-				dummy.fac_out.u.CCBSRequest.InvokeID = cc_record->invoke_id;
-				dummy.fac_out.u.CCBSRequest.ComponentType = FacComponent_Invoke;
-				dummy.fac_out.u.CCBSRequest.Component.Invoke.CallLinkageID =
-					cc_record->mode.ptmp.linkage_id;
-
-				/* Send message */
-				print_facility(&dummy.fac_out, &dummy);
-				misdn_lib_send_event(&dummy, EVENT_FACILITY);
-			}
-		}
-	}
-	AST_LIST_UNLOCK(&misdn_cc_records_db);
-
-	/* Wait for the response to the call completion request. */
-	misdn_cc_response_wait(chan, MISDN_CC_REQUEST_WAIT_MAX, record_id);
-
-	AST_LIST_LOCK(&misdn_cc_records_db);
-	cc_record = misdn_cc_find_by_id(record_id);
-	if (cc_record) {
-		if (!cc_record->activated) {
-			if (cc_record->port < 0) {
-				/* The network did not tell us that call completion was available. */
-				error_str = "No port number";
-			} else if (cc_record->outstanding_message) {
-				cc_record->outstanding_message = 0;
-				error_str = misdn_no_response_from_network;
-			} else if (cc_record->reject_code != FacReject_None) {
-				error_str = misdn_to_str_reject_code(cc_record->reject_code);
-			} else if (cc_record->error_code != FacError_None) {
-				error_str = misdn_to_str_error_code(cc_record->error_code);
-			} else if (cc_record->ptp) {
-				if (cc_record->mode.ptp.bc) {
-					error_str = "Call-completion already requested";
-				} else {
-					error_str = "Could not allocate call-completion signaling link";
-				}
-			} else {
-				/* Should never happen. */
-				error_str = "Unexpected error";
-			}
-
-			/* No need to keep the call completion record. */
-			if (cc_record->ptp && cc_record->mode.ptp.bc) {
-				/* Close the call-completion signaling link */
-				bc = cc_record->mode.ptp.bc;
-				bc->fac_out.Function = Fac_None;
-				bc->out_cause = AST_CAUSE_NORMAL_CLEARING;
-				misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE);
-			}
-			misdn_cc_delete(cc_record);
-		} else {
-			error_str = NULL;
-		}
-	} else {
-		error_str = misdn_cc_record_not_found;
-	}
-	AST_LIST_UNLOCK(&misdn_cc_records_db);
-	if (error_str) {
-		ast_verb(1, "%s(%s) diagnostic '%s' on channel %s\n",
-			misdn_command_name, subcommand->name, error_str, ast_channel_name(chan));
-		pbx_builtin_setvar_helper(chan, MISDN_ERROR_MSG, error_str);
-		pbx_builtin_setvar_helper(chan, MISDN_CC_STATUS, "ERROR");
-	} else {
-		pbx_builtin_setvar_helper(chan, MISDN_CC_STATUS, "ACTIVATED");
-	}
-
-	return 0;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief misdn_command(ccbs-request) subcommand handler
- *
- * \verbatim
-   misdn_command(ccbs-request,${MISDN_CC_RECORD_ID},<notify-context>,<user-a-extension>,<priority>)
-   \endverbatim
- * Set the dialplan location to notify when User-B is free and User-A is free.
- *
- * \param chan Asterisk channel to operate upon.
- * \param subcommand Arguments for the subcommand
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int misdn_command_ccbs_request(struct ast_channel *chan, struct misdn_command_args *subcommand)
-{
-	static const struct misdn_cc_request request = {
-		.ptmp = Fac_CCBSRequest,
-		.ptp = Fac_CCBS_T_Request
-	};
-
-	return misdn_command_cc_request(chan, subcommand, &request);
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief misdn_command(ccnr-request) subcommand handler
- *
- * \verbatim
-   misdn_command(ccnr-request,${MISDN_CC_RECORD_ID},<notify-context>,<user-a-extension>,<priority>)
-   \endverbatim
- * Set the dialplan location to notify when User-B is free and User-A is free.
- *
- * \param chan Asterisk channel to operate upon.
- * \param subcommand Arguments for the subcommand
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int misdn_command_ccnr_request(struct ast_channel *chan, struct misdn_command_args *subcommand)
-{
-	static const struct misdn_cc_request request = {
-		.ptmp = Fac_CCNRRequest,
-		.ptp = Fac_CCNR_T_Request
-	};
-
-	return misdn_command_cc_request(chan, subcommand, &request);
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-struct misdn_command_table {
-	/*! \brief subcommand name */
-	const char *name;
-
-	/*! \brief subcommand handler */
-	int (*func)(struct ast_channel *chan, struct misdn_command_args *subcommand);
-
-	/*! \brief TRUE if the subcommand can only be executed on mISDN channels */
-	int misdn_only;
-};
-static const struct misdn_command_table misdn_commands[] = {
-/* *INDENT-OFF* */
-	/* subcommand-name  subcommand-handler              mISDN only */
-	{ "cc-initialize",  misdn_command_cc_initialize,    0 },
-	{ "cc-deactivate",  misdn_command_cc_deactivate,    0 },
-	{ "cc-a-busy",      misdn_command_cc_a_busy,        0 },
-	{ "cc-b-free",      misdn_command_cc_b_free,        0 },
-	{ "ccbs-request",   misdn_command_ccbs_request,     0 },
-	{ "ccnr-request",   misdn_command_ccnr_request,     0 },
-/* *INDENT-ON* */
-};
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief misdn_command() dialplan application.
- *
- * \param chan Asterisk channel to operate upon.
- * \param data Application options string.
- *
- * \retval 0 on success.
- * \retval -1 on error.
- */
-static int misdn_command_exec(struct ast_channel *chan, const char *data)
-{
-	char *parse;
-	unsigned index;
-	struct misdn_command_args subcommand;
-
-	if (ast_strlen_zero((char *) data)) {
-		ast_log(LOG_ERROR, "%s requires arguments\n", misdn_command_name);
-		return -1;
-	}
-
-	ast_debug(1, "%s(%s)\n", misdn_command_name, (char *) data);
-
-	parse = ast_strdupa(data);
-	AST_STANDARD_APP_ARGS(subcommand, parse);
-	if (!subcommand.argc || ast_strlen_zero(subcommand.name)) {
-		ast_log(LOG_ERROR, "%s requires a subcommand\n", misdn_command_name);
-		return -1;
-	}
-
-	for (index = 0; index < ARRAY_LEN(misdn_commands); ++index) {
-		if (strcasecmp(misdn_commands[index].name, subcommand.name) == 0) {
-			strcpy(subcommand.name, misdn_commands[index].name);
-			if (misdn_commands[index].misdn_only
-				&& strcasecmp(ast_channel_tech(chan)->type, misdn_type) != 0) {
-				ast_log(LOG_WARNING,
-					"%s(%s) only makes sense with %s channels!\n",
-					misdn_command_name, subcommand.name, misdn_type);
-				return -1;
-			}
-			return misdn_commands[index].func(chan, &subcommand);
-		}
-	}
-
-	ast_log(LOG_WARNING, "%s(%s) subcommand is unknown\n", misdn_command_name,
-		subcommand.name);
-	return -1;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-static int misdn_facility_exec(struct ast_channel *chan, const char *data)
-{
-	struct chan_list *ch = MISDN_ASTERISK_TECH_PVT(chan);
-	char *parse;
-	unsigned max_len;
-
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(facility_type);
-		AST_APP_ARG(arg)[99];
-	);
-
-	chan_misdn_log(0, 0, "TYPE: %s\n", ast_channel_tech(chan)->type);
-
-	if (strcasecmp(ast_channel_tech(chan)->type, misdn_type)) {
-		ast_log(LOG_WARNING, "misdn_facility only makes sense with %s channels!\n", misdn_type);
-		return -1;
-	}
-
-	if (ast_strlen_zero((char *) data)) {
-		ast_log(LOG_WARNING, "misdn_facility requires arguments: facility_type[,<args>]\n");
-		return -1;
-	}
-
-	parse = ast_strdupa(data);
-	AST_STANDARD_APP_ARGS(args, parse);
-
-	if (ast_strlen_zero(args.facility_type)) {
-		ast_log(LOG_WARNING, "misdn_facility requires arguments: facility_type[,<args>]\n");
-		return -1;
-	}
-
-	if (!strcasecmp(args.facility_type, "calldeflect")) {
-		if (ast_strlen_zero(args.arg[0])) {
-			ast_log(LOG_WARNING, "Facility: Call Deflection requires an argument: Number\n");
-		}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-		max_len = sizeof(ch->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.Number) - 1;
-		if (max_len < strlen(args.arg[0])) {
-			ast_log(LOG_WARNING,
-				"Facility: Number argument too long (up to %u digits are allowed). Ignoring.\n",
-				max_len);
-			return 0;
-		}
-		ch->bc->fac_out.Function = Fac_CallDeflection;
-		ch->bc->fac_out.u.CallDeflection.InvokeID = ++misdn_invoke_id;
-		ch->bc->fac_out.u.CallDeflection.ComponentType = FacComponent_Invoke;
-		ch->bc->fac_out.u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUserPresent = 1;
-		ch->bc->fac_out.u.CallDeflection.Component.Invoke.PresentationAllowedToDivertedToUser = 0;
-		ch->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.Type = 0;/* unknown */
-		ch->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.LengthOfNumber = strlen(args.arg[0]);
-		strcpy((char *) ch->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Party.Number, args.arg[0]);
-		ch->bc->fac_out.u.CallDeflection.Component.Invoke.Deflection.Subaddress.Length = 0;
-
-#else	/* !defined(AST_MISDN_ENHANCEMENTS) */
-
-		max_len = sizeof(ch->bc->fac_out.u.CDeflection.DeflectedToNumber) - 1;
-		if (max_len < strlen(args.arg[0])) {
-			ast_log(LOG_WARNING,
-				"Facility: Number argument too long (up to %u digits are allowed). Ignoring.\n",
-				max_len);
-			return 0;
-		}
-		ch->bc->fac_out.Function = Fac_CD;
-		ch->bc->fac_out.u.CDeflection.PresentationAllowed = 0;
-		//ch->bc->fac_out.u.CDeflection.DeflectedToSubaddress[0] = 0;
-		strcpy((char *) ch->bc->fac_out.u.CDeflection.DeflectedToNumber, args.arg[0]);
-#endif	/* !defined(AST_MISDN_ENHANCEMENTS) */
-
-		/* Send message */
-		print_facility(&ch->bc->fac_out, ch->bc);
-		misdn_lib_send_event(ch->bc, EVENT_FACILITY);
-#if defined(AST_MISDN_ENHANCEMENTS)
-	} else if (!strcasecmp(args.facility_type, "callrerouteing")
-		|| !strcasecmp(args.facility_type, "callrerouting")) {
-		if (ast_strlen_zero(args.arg[0])) {
-			ast_log(LOG_WARNING, "Facility: Call rerouting requires an argument: Number\n");
-		}
-
-		max_len = sizeof(ch->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number) - 1;
-		if (max_len < strlen(args.arg[0])) {
-			ast_log(LOG_WARNING,
-				"Facility: Number argument too long (up to %u digits are allowed). Ignoring.\n",
-				max_len);
-			return 0;
-		}
-		ch->bc->fac_out.Function = Fac_CallRerouteing;
-		ch->bc->fac_out.u.CallRerouteing.InvokeID = ++misdn_invoke_id;
-		ch->bc->fac_out.u.CallRerouteing.ComponentType = FacComponent_Invoke;
-
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.ReroutingReason = 0;/* unknown */
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.ReroutingCounter = 1;
-
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.Type = 0;/* unknown */
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.LengthOfNumber = strlen(args.arg[0]);
-		strcpy((char *) ch->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Party.Number, args.arg[0]);
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.CalledAddress.Subaddress.Length = 0;
-
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.CallingPartySubaddress.Length = 0;
-
-		/* 0x90 0x90 0xa3 3.1 kHz audio, circuit mode, 64kbit/sec, level1/a-Law */
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Length = 3;
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents[0] = 0x90;
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents[1] = 0x90;
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Bc.Contents[2] = 0xa3;
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Hlc.Length = 0;
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.Llc.Length = 0;
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.Q931ie.UserInfo.Length = 0;
-
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.LastRerouting.Type = 1;/* presentationRestricted */
-		ch->bc->fac_out.u.CallRerouteing.Component.Invoke.SubscriptionOption = 0;/* no notification to caller */
-
-		/* Send message */
-		print_facility(&ch->bc->fac_out, ch->bc);
-		misdn_lib_send_event(ch->bc, EVENT_FACILITY);
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	} else {
-		chan_misdn_log(1, ch->bc->port, "Unknown Facility: %s\n", args.facility_type);
-	}
-
-	return 0;
-}
-
-static int misdn_check_l2l1(struct ast_channel *chan, const char *data)
-{
-	char *parse;
-	char group[BUFFERSIZE + 1];
-	char *port_str;
-	int port = 0;
-	int timeout;
-	int dowait = 0;
-	int port_up;
-
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(grouppar);
-		AST_APP_ARG(timeout);
-	);
-
-	if (ast_strlen_zero((char *) data)) {
-		ast_log(LOG_WARNING, "misdn_check_l2l1 Requires arguments\n");
-		return -1;
-	}
-
-	parse = ast_strdupa(data);
-	AST_STANDARD_APP_ARGS(args, parse);
-
-	if (args.argc != 2) {
-		ast_log(LOG_WARNING, "Wrong argument count\n");
-		return 0;
-	}
-
-	/*ast_log(LOG_NOTICE, "Arguments: group/port '%s' timeout '%s'\n", args.grouppar, args.timeout);*/
-	timeout = atoi(args.timeout);
-	port_str = args.grouppar;
-
-	if (port_str[0] == 'g' && port_str[1] == ':') {
-		/* We make a group call lets checkout which ports are in my group */
-		port_str += 2;
-		ast_copy_string(group, port_str, sizeof(group));
-		chan_misdn_log(2, 0, "Checking Ports in group: %s\n", group);
-
-		for (port = misdn_cfg_get_next_port(port);
-			port > 0;
-			port = misdn_cfg_get_next_port(port)) {
-			char cfg_group[BUFFERSIZE + 1];
-
-			chan_misdn_log(2, 0, "trying port %d\n", port);
-
-			misdn_cfg_get(port, MISDN_CFG_GROUPNAME, cfg_group, sizeof(cfg_group));
-
-			if (!strcasecmp(cfg_group, group)) {
-				port_up = misdn_lib_port_up(port, 1);
-				if (!port_up) {
-					chan_misdn_log(2, 0, " --> port '%d'\n", port);
-					misdn_lib_get_port_up(port);
-					dowait = 1;
-				}
-			}
-		}
-	} else {
-		port = atoi(port_str);
-		chan_misdn_log(2, 0, "Checking Port: %d\n", port);
-		port_up = misdn_lib_port_up(port, 1);
-		if (!port_up) {
-			misdn_lib_get_port_up(port);
-			dowait = 1;
-		}
-	}
-
-	if (dowait) {
-		chan_misdn_log(2, 0, "Waiting for '%d' seconds\n", timeout);
-		ast_safe_sleep(chan, timeout * 1000);
-	}
-
-	return 0;
-}
-
-static int misdn_set_opt_exec(struct ast_channel *chan, const char *data)
-{
-	struct chan_list *ch = MISDN_ASTERISK_TECH_PVT(chan);
-	char *tok;
-	char *tokb;
-	char *parse;
-	int keyidx = 0;
-	int rxgain = 0;
-	int txgain = 0;
-	int change_jitter = 0;
-
-	if (strcasecmp(ast_channel_tech(chan)->type, misdn_type)) {
-		ast_log(LOG_WARNING, "misdn_set_opt makes sense only with %s channels!\n", misdn_type);
-		return -1;
-	}
-
-	if (ast_strlen_zero((char *) data)) {
-		ast_log(LOG_WARNING, "misdn_set_opt Requires arguments\n");
-		return -1;
-	}
-
-	parse = ast_strdupa(data);
-	for (tok = strtok_r(parse, ":", &tokb);
-		tok;
-		tok = strtok_r(NULL, ":", &tokb)) {
-		int neglect = 0;
-
-		if (tok[0] == '!') {
-			neglect = 1;
-			tok++;
-		}
-
-		switch(tok[0]) {
-		case 'd' :
-			ast_copy_string(ch->bc->display, ++tok, sizeof(ch->bc->display));
-			chan_misdn_log(1, ch->bc->port, "SETOPT: Display:%s\n", ch->bc->display);
-			break;
-		case 'n':
-			chan_misdn_log(1, ch->bc->port, "SETOPT: No DSP\n");
-			ch->bc->nodsp = 1;
-			break;
-		case 'j':
-			chan_misdn_log(1, ch->bc->port, "SETOPT: jitter\n");
-			tok++;
-			change_jitter = 1;
-
-			switch (tok[0]) {
-			case 'b':
-				ch->jb_len = atoi(++tok);
-				chan_misdn_log(1, ch->bc->port, " --> buffer_len:%d\n", ch->jb_len);
-				break;
-			case 't' :
-				ch->jb_upper_threshold = atoi(++tok);
-				chan_misdn_log(1, ch->bc->port, " --> upper_threshold:%d\n", ch->jb_upper_threshold);
-				break;
-			case 'n':
-				ch->bc->nojitter = 1;
-				chan_misdn_log(1, ch->bc->port, " --> nojitter\n");
-				break;
-			default:
-				ch->jb_len = 4000;
-				ch->jb_upper_threshold = 0;
-				chan_misdn_log(1, ch->bc->port, " --> buffer_len:%d (default)\n", ch->jb_len);
-				chan_misdn_log(1, ch->bc->port, " --> upper_threshold:%d (default)\n", ch->jb_upper_threshold);
-				break;
-			}
-			break;
-		case 'v':
-			tok++;
-
-			switch (tok[0]) {
-			case 'r' :
-				rxgain = atoi(++tok);
-				if (rxgain < -8) {
-					rxgain = -8;
-				}
-				if (rxgain > 8) {
-					rxgain = 8;
-				}
-				ch->bc->rxgain = rxgain;
-				chan_misdn_log(1, ch->bc->port, "SETOPT: Volume:%d\n", rxgain);
-				break;
-			case 't':
-				txgain = atoi(++tok);
-				if (txgain < -8) {
-					txgain = -8;
-				}
-				if (txgain > 8) {
-					txgain = 8;
-				}
-				ch->bc->txgain = txgain;
-				chan_misdn_log(1, ch->bc->port, "SETOPT: Volume:%d\n", txgain);
-				break;
-			}
-			break;
-		case 'c':
-			keyidx = atoi(++tok);
-			{
-				char keys[4096];
-				char *key = NULL;
-				char *tmp = keys;
-				int i;
-
-				misdn_cfg_get(0, MISDN_GEN_CRYPT_KEYS, keys, sizeof(keys));
-
-				for (i = 0; i < keyidx; i++) {
-					key = strsep(&tmp, ",");
-				}
-
-				if (key) {
-					ast_copy_string(ch->bc->crypt_key, key, sizeof(ch->bc->crypt_key));
-				}
-
-				chan_misdn_log(0, ch->bc->port, "SETOPT: crypt with key:%s\n", ch->bc->crypt_key);
-				break;
-			}
-		case 'e':
-			chan_misdn_log(1, ch->bc->port, "SETOPT: EchoCancel\n");
-
-			if (neglect) {
-				chan_misdn_log(1, ch->bc->port, " --> disabled\n");
-#ifdef MISDN_1_2
-				*ch->bc->pipeline = 0;
-#else
-				ch->bc->ec_enable = 0;
-#endif
-			} else {
-#ifdef MISDN_1_2
-				update_pipeline_config(ch->bc);
-#else
-				ch->bc->ec_enable = 1;
-				ch->bc->orig = ch->originator;
-				tok++;
-				if (*tok) {
-					ch->bc->ec_deftaps = atoi(tok);
-				}
-#endif
-			}
-			break;
-		case 'h':
-			chan_misdn_log(1, ch->bc->port, "SETOPT: Digital\n");
-
-			if (strlen(tok) > 1 && tok[1] == '1') {
-				chan_misdn_log(1, ch->bc->port, "SETOPT: HDLC \n");
-				if (!ch->bc->hdlc) {
-					ch->bc->hdlc = 1;
-				}
-			}
-			ch->bc->capability = INFO_CAPABILITY_DIGITAL_UNRESTRICTED;
-			break;
-		case 's':
-			chan_misdn_log(1, ch->bc->port, "SETOPT: Send DTMF\n");
-			ch->bc->send_dtmf = 1;
-			break;
-		case 'f':
-			chan_misdn_log(1, ch->bc->port, "SETOPT: Faxdetect\n");
-			ch->faxdetect = 1;
-			misdn_cfg_get(ch->bc->port, MISDN_CFG_FAXDETECT_TIMEOUT, &ch->faxdetect_timeout, sizeof(ch->faxdetect_timeout));
-			break;
-		case 'a':
-			chan_misdn_log(1, ch->bc->port, "SETOPT: AST_DSP (for DTMF)\n");
-			ch->ast_dsp = 1;
-			break;
-		case 'p':
-			chan_misdn_log(1, ch->bc->port, "SETOPT: callerpres: %s\n", &tok[1]);
-			/* CRICH: callingpres!!! */
-			if (strstr(tok, "allowed")) {
-				ch->bc->presentation = 0;
-				ch->bc->set_presentation = 1;
-			} else if (strstr(tok, "restricted")) {
-				ch->bc->presentation = 1;
-				ch->bc->set_presentation = 1;
-			} else if (strstr(tok, "not_screened")) {
-				chan_misdn_log(0, ch->bc->port, "SETOPT: callerpres: not_screened is deprecated\n");
-				ch->bc->presentation = 1;
-				ch->bc->set_presentation = 1;
-			}
-			break;
-	  	case 'i' :
-			chan_misdn_log(1, ch->bc->port, "Ignoring dtmf tones, just use them inband\n");
-			ch->ignore_dtmf = 1;
-			break;
-		default:
-			break;
-		}
-	}
-
-	if (change_jitter) {
-		config_jitterbuffer(ch);
-	}
-
-	if (ch->faxdetect || ch->ast_dsp) {
-		if (!ch->dsp) {
-			ch->dsp = ast_dsp_new();
-		}
-		if (ch->dsp) {
-			ast_dsp_set_features(ch->dsp, DSP_FEATURE_DIGIT_DETECT | DSP_FEATURE_FAX_DETECT);
-		}
-	}
-
-	if (ch->ast_dsp) {
-		chan_misdn_log(1, ch->bc->port, "SETOPT: with AST_DSP we deactivate mISDN_dsp\n");
-		ch->bc->nodsp = 1;
-	}
-
-	return 0;
-}
-
-
-int chan_misdn_jb_empty(struct misdn_bchannel *bc, char *buf, int len)
-{
-	struct chan_list *ch;
-	int res;
-
-	ch = find_chan_by_bc(bc);
-	if (!ch) {
-		return 0;
-	}
-
-	if (ch->jb) {
-		res = misdn_jb_empty(ch->jb, buf, len);
-	} else {
-		res = 0;
-	}
-	chan_list_unref(ch, "Done emptying jb");
-
-	return res;
-}
-
-
-
-/*******************************************************/
-/***************** JITTERBUFFER ************************/
-/*******************************************************/
-
-
-/* allocates the jb-structure and initialize the elements*/
-struct misdn_jb *misdn_jb_init(int size, int upper_threshold)
-{
-	struct misdn_jb *jb;
-
-	jb = ast_calloc(1, sizeof(*jb));
-	if (!jb) {
-	    chan_misdn_log(-1, 0, "No free Mem for jb\n");
-	    return NULL;
-	}
-	jb->size = size;
-	jb->upper_threshold = upper_threshold;
-	//jb->wp = 0;
-	//jb->rp = 0;
-	//jb->state_full = 0;
-	//jb->state_empty = 0;
-	//jb->bytes_wrote = 0;
-	jb->samples = ast_calloc(size, sizeof(*jb->samples));
-	if (!jb->samples) {
-		ast_free(jb);
-		chan_misdn_log(-1, 0, "No free Mem for jb->samples\n");
-		return NULL;
-	}
-
-	jb->ok = ast_calloc(size, sizeof(*jb->ok));
-	if (!jb->ok) {
-		ast_free(jb->samples);
-		ast_free(jb);
-		chan_misdn_log(-1, 0, "No free Mem for jb->ok\n");
-		return NULL;
-	}
-
-	ast_mutex_init(&jb->mutexjb);
-
-	return jb;
-}
-
-/* frees the data and destroys the given jitterbuffer struct */
-void misdn_jb_destroy(struct misdn_jb *jb)
-{
-	ast_mutex_destroy(&jb->mutexjb);
-
-	ast_free(jb->ok);
-	ast_free(jb->samples);
-	ast_free(jb);
-}
-
-/* fills the jitterbuffer with len data returns < 0 if there was an
-   error (buffer overflow). */
-int misdn_jb_fill(struct misdn_jb *jb, const char *data, int len)
-{
-	int i;
-	int j;
-	int rp;
-	int wp;
-
-	if (!jb || ! data) {
-		return 0;
-	}
-
-	ast_mutex_lock(&jb->mutexjb);
-
-	wp = jb->wp;
-	rp = jb->rp;
-
-	for (i = 0; i < len; i++) {
-		jb->samples[wp] = data[i];
-		jb->ok[wp] = 1;
-		wp = (wp != jb->size - 1) ? wp + 1 : 0;
-
-		if (wp == jb->rp) {
-			jb->state_full = 1;
-		}
-	}
-
-	if (wp >= rp) {
-		jb->state_buffer = wp - rp;
-	} else {
-		jb->state_buffer = jb->size - rp + wp;
-	}
-	chan_misdn_log(9, 0, "misdn_jb_fill: written:%d | Buffer status:%d p:%p\n", len, jb->state_buffer, jb);
-
-	if (jb->state_full) {
-		jb->wp = wp;
-
-		rp = wp;
-		for (j = 0; j < jb->upper_threshold; j++) {
-			rp = (rp != 0) ? rp - 1 : jb->size - 1;
-		}
-		jb->rp = rp;
-		jb->state_full = 0;
-		jb->state_empty = 1;
-
-		ast_mutex_unlock(&jb->mutexjb);
-
-		return -1;
-	}
-
-	if (!jb->state_empty) {
-		jb->bytes_wrote += len;
-		if (jb->bytes_wrote >= jb->upper_threshold) {
-			jb->state_empty = 1;
-			jb->bytes_wrote = 0;
-		}
-	}
-	jb->wp = wp;
-
-	ast_mutex_unlock(&jb->mutexjb);
-
-	return 0;
-}
-
-/* gets len bytes out of the jitterbuffer if available, else only the
-available data is returned and the return value indicates the number
-of data. */
-int misdn_jb_empty(struct misdn_jb *jb, char *data, int len)
-{
-	int i;
-	int wp;
-	int rp;
-	int read = 0;
-
-	ast_mutex_lock(&jb->mutexjb);
-
-	rp = jb->rp;
-	wp = jb->wp;
-
-	if (jb->state_empty) {
-		for (i = 0; i < len; i++) {
-			if (wp == rp) {
-				jb->rp = rp;
-				jb->state_empty = 0;
-
-				ast_mutex_unlock(&jb->mutexjb);
-
-				return read;
-			} else {
-				if (jb->ok[rp] == 1) {
-					data[i] = jb->samples[rp];
-					jb->ok[rp] = 0;
-					rp = (rp != jb->size - 1) ? rp + 1 : 0;
-					read += 1;
-				}
-			}
-		}
-
-		if (wp >= rp) {
-			jb->state_buffer = wp - rp;
-		} else {
-			jb->state_buffer = jb->size - rp + wp;
-		}
-		chan_misdn_log(9, 0, "misdn_jb_empty: read:%d | Buffer status:%d p:%p\n", len, jb->state_buffer, jb);
-
-		jb->rp = rp;
-	} else {
-		chan_misdn_log(9, 0, "misdn_jb_empty: Wait...requested:%d p:%p\n", len, jb);
-	}
-
-	ast_mutex_unlock(&jb->mutexjb);
-
-	return read;
-}
-
-/*******************************************************/
-/*************** JITTERBUFFER  END *********************/
-/*******************************************************/
-
-static void chan_misdn_log(int level, int port, char *tmpl, ...)
-{
-	va_list ap;
-	char buf[1024];
-	char port_buf[8];
-
-	if (!(0 <= port && port <= max_ports)) {
-		ast_log(LOG_WARNING, "chan_misdn_log called with out-of-range port number! (%d)\n", port);
-		port = 0;
-		level = -1;
-	} else if (!(level == -1
-		|| (misdn_debug_only[port]
-			? (level == 1 && misdn_debug[port]) || level == misdn_debug[port]
-			: level <= misdn_debug[port])
-		|| (level <= misdn_debug[0] && !ast_strlen_zero(global_tracefile)))) {
-		/*
-		 * We are not going to print anything so lets not
-		 * go to all the work of generating a string.
-		 */
-		return;
-	}
-
-	snprintf(port_buf, sizeof(port_buf), "P[%2d] ", port);
-	va_start(ap, tmpl);
-	vsnprintf(buf, sizeof(buf), tmpl, ap);
-	va_end(ap);
-
-	if (level == -1) {
-		ast_log(LOG_WARNING, "%s", buf);
-	} else if (misdn_debug_only[port]
-		? (level == 1 && misdn_debug[port]) || level == misdn_debug[port]
-		: level <= misdn_debug[port]) {
-		ast_verbose("%s%s", port_buf, buf);
-	}
-
-	if (level <= misdn_debug[0] && !ast_strlen_zero(global_tracefile)) {
-		char ctimebuf[30];
-		time_t tm;
-		char *tmp;
-		char *p;
-		FILE *fp;
-
-		fp = fopen(global_tracefile, "a+");
-		if (!fp) {
-			ast_verbose("Error opening Tracefile: [ %s ] %s\n", global_tracefile, strerror(errno));
-			return;
-		}
-
-		tm = time(NULL);
-		tmp = ctime_r(&tm, ctimebuf);
-		p = strchr(tmp, '\n');
-		if (p) {
-			*p = ':';
-		}
-		fputs(tmp, fp);
-		fputs(" ", fp);
-		fputs(port_buf, fp);
-		fputs(" ", fp);
-		fputs(buf, fp);
-
-		fclose(fp);
-	}
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Channel driver for mISDN Support (BRI/PRI)",
-	.support_level = AST_MODULE_SUPPORT_DEPRECATED,
-	.load = load_module,
-	.unload = unload_module,
-	.reload = reload,
-	.load_pri = AST_MODPRI_CHANNEL_DRIVER,
-);
diff --git a/channels/chan_nbs.c b/channels/chan_nbs.c
deleted file mode 100644
index c7c842ae34c433b591771f112a89626a4568e1bd..0000000000000000000000000000000000000000
--- a/channels/chan_nbs.c
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2006, Digium, Inc.
- *
- * Mark Spencer <markster@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Network broadcast sound support channel driver
- *
- * \author Mark Spencer <markster@digium.com>
- *
- * \ingroup channel_drivers
- */
-
-/*** MODULEINFO
-	<depend>nbs</depend>
-	<support_level>deprecated</support_level>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include "asterisk.h"
-
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <arpa/inet.h>
-#include <fcntl.h>
-#include <sys/ioctl.h>
-#include <nbs.h>
-
-#include "asterisk/lock.h"
-#include "asterisk/channel.h"
-#include "asterisk/config.h"
-#include "asterisk/module.h"
-#include "asterisk/pbx.h"
-#include "asterisk/utils.h"
-#include "asterisk/format_cache.h"
-
-static const char tdesc[] = "Network Broadcast Sound Driver";
-
-static char context[AST_MAX_EXTENSION] = "default";
-static const char type[] = "NBS";
-
-/* NBS creates private structures on demand */
-
-struct nbs_pvt {
-	NBS *nbs;
-	struct ast_channel *owner;		/* Channel we belong to, possibly NULL */
-	char app[16];					/* Our app */
-	char stream[80];				/* Our stream */
-	struct ast_module_user *u;		/*! for holding a reference to this module */
-};
-
-static struct ast_channel *nbs_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause);
-static int nbs_call(struct ast_channel *ast, const char *dest, int timeout);
-static int nbs_hangup(struct ast_channel *ast);
-static struct ast_frame *nbs_xread(struct ast_channel *ast);
-static int nbs_xwrite(struct ast_channel *ast, struct ast_frame *frame);
-
-static struct ast_channel_tech nbs_tech = {
-	.type = type,
-	.description = tdesc,
-	.requester = nbs_request,
-	.call = nbs_call,
-	.hangup = nbs_hangup,
-	.read = nbs_xread,
-	.write = nbs_xwrite,
-};
-
-static int nbs_call(struct ast_channel *ast, const char *dest, int timeout)
-{
-	struct nbs_pvt *p;
-
-	p = ast_channel_tech_pvt(ast);
-
-	if ((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) {
-		ast_log(LOG_WARNING, "nbs_call called on %s, neither down nor reserved\n", ast_channel_name(ast));
-		return -1;
-	}
-	/* When we call, it just works, really, there's no destination...  Just
-	   ring the phone and wait for someone to answer */
-	ast_debug(1, "Calling %s on %s\n", dest, ast_channel_name(ast));
-
-	/* If we can't connect, return congestion */
-	if (nbs_connect(p->nbs)) {
-		ast_log(LOG_WARNING, "NBS Connection failed on %s\n", ast_channel_name(ast));
-		ast_queue_control(ast, AST_CONTROL_CONGESTION);
-	} else {
-		ast_setstate(ast, AST_STATE_RINGING);
-		ast_queue_control(ast, AST_CONTROL_ANSWER);
-	}
-
-	return 0;
-}
-
-static void nbs_destroy(struct nbs_pvt *p)
-{
-	if (p->nbs)
-		nbs_delstream(p->nbs);
-	ast_module_user_remove(p->u);
-	ast_free(p);
-}
-
-static struct nbs_pvt *nbs_alloc(const char *data)
-{
-	struct nbs_pvt *p;
-	int flags = 0;
-	char stream[256];
-	char *opts;
-
-	ast_copy_string(stream, data, sizeof(stream));
-	if ((opts = strchr(stream, ':'))) {
-		*opts = '\0';
-		opts++;
-	} else
-		opts = "";
-	p = ast_calloc(1, sizeof(*p));
-	if (p) {
-		if (!ast_strlen_zero(opts)) {
-			if (strchr(opts, 'm'))
-				flags |= NBS_FLAG_MUTE;
-			if (strchr(opts, 'o'))
-				flags |= NBS_FLAG_OVERSPEAK;
-			if (strchr(opts, 'e'))
-				flags |= NBS_FLAG_EMERGENCY;
-			if (strchr(opts, 'O'))
-				flags |= NBS_FLAG_OVERRIDE;
-		} else
-			flags = NBS_FLAG_OVERSPEAK;
-
-		ast_copy_string(p->stream, stream, sizeof(p->stream));
-		p->nbs = nbs_newstream("asterisk", stream, flags);
-		if (!p->nbs) {
-			ast_log(LOG_WARNING, "Unable to allocate new NBS stream '%s' with flags %d\n", stream, flags);
-			ast_free(p);
-			p = NULL;
-		} else {
-			/* Set for 8000 hz mono, 640 samples */
-			nbs_setbitrate(p->nbs, 8000);
-			nbs_setchannels(p->nbs, 1);
-			nbs_setblocksize(p->nbs, 640);
-			nbs_setblocking(p->nbs, 0);
-		}
-	}
-	return p;
-}
-
-static int nbs_hangup(struct ast_channel *ast)
-{
-	struct nbs_pvt *p;
-	p = ast_channel_tech_pvt(ast);
-	ast_debug(1, "nbs_hangup(%s)\n", ast_channel_name(ast));
-	if (!ast_channel_tech_pvt(ast)) {
-		ast_log(LOG_WARNING, "Asked to hangup channel not connected\n");
-		return 0;
-	}
-	nbs_destroy(p);
-	ast_channel_tech_pvt_set(ast, NULL);
-	ast_setstate(ast, AST_STATE_DOWN);
-	return 0;
-}
-
-static struct ast_frame  *nbs_xread(struct ast_channel *ast)
-{
-	ast_debug(1, "Returning null frame on %s\n", ast_channel_name(ast));
-
-	return &ast_null_frame;
-}
-
-static int nbs_xwrite(struct ast_channel *ast, struct ast_frame *frame)
-{
-	struct nbs_pvt *p = ast_channel_tech_pvt(ast);
-	if (ast_channel_state(ast) != AST_STATE_UP) {
-		/* Don't try tos end audio on-hook */
-		return 0;
-	}
-	if (nbs_write(p->nbs, frame->data.ptr, frame->datalen / 2) < 0)
-		return -1;
-	return 0;
-}
-
-static struct ast_channel *nbs_new(struct nbs_pvt *i, int state, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor)
-{
-	struct ast_channel *tmp;
-	tmp = ast_channel_alloc(1, state, 0, 0, "", "s", context, assignedids, requestor, 0, "NBS/%s", i->stream);
-	if (tmp) {
-		ast_channel_tech_set(tmp, &nbs_tech);
-		ast_channel_set_fd(tmp, 0, nbs_fd(i->nbs));
-
-		ast_channel_nativeformats_set(tmp, nbs_tech.capabilities);
-		ast_channel_set_rawreadformat(tmp, ast_format_slin);
-		ast_channel_set_rawwriteformat(tmp, ast_format_slin);
-		ast_channel_set_writeformat(tmp, ast_format_slin);
-		ast_channel_set_readformat(tmp, ast_format_slin);
-		if (state == AST_STATE_RING)
-			ast_channel_rings_set(tmp, 1);
-		ast_channel_tech_pvt_set(tmp, i);
-		ast_channel_context_set(tmp, context);
-		ast_channel_exten_set(tmp, "s");
-		ast_channel_language_set(tmp, "");
-		i->owner = tmp;
-		i->u = ast_module_user_add(tmp);
-		ast_channel_unlock(tmp);
-		if (state != AST_STATE_DOWN) {
-			if (ast_pbx_start(tmp)) {
-				ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(tmp));
-				ast_hangup(tmp);
-			}
-		}
-	} else
-		ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
-	return tmp;
-}
-
-
-static struct ast_channel *nbs_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
-{
-	struct nbs_pvt *p;
-	struct ast_channel *tmp = NULL;
-
-	if (ast_format_cap_iscompatible_format(cap, ast_format_slin) == AST_FORMAT_CMP_NOT_EQUAL) {
-		struct ast_str *cap_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
-
-		ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%s'\n",
-			ast_format_cap_get_names(cap, &cap_buf));
-		return NULL;
-	}
-	p = nbs_alloc(data);
-	if (p) {
-		tmp = nbs_new(p, AST_STATE_DOWN, assignedids, requestor);
-		if (!tmp)
-			nbs_destroy(p);
-	}
-	return tmp;
-}
-
-static int unload_module(void)
-{
-	/* First, take us out of the channel loop */
-	ast_channel_unregister(&nbs_tech);
-	ao2_ref(nbs_tech.capabilities, -1);
-	nbs_tech.capabilities = NULL;
-	return 0;
-}
-
-static int load_module(void)
-{
-	if (!(nbs_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	ast_format_cap_append(nbs_tech.capabilities, ast_format_slin, 0);
-	/* Make sure we can register our channel type */
-	if (ast_channel_register(&nbs_tech)) {
-		ast_log(LOG_ERROR, "Unable to register channel class %s\n", type);
-		ao2_ref(nbs_tech.capabilities, -1);
-		nbs_tech.capabilities = NULL;
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	return AST_MODULE_LOAD_SUCCESS;
-}
-
-AST_MODULE_INFO_STANDARD_DEPRECATED(ASTERISK_GPL_KEY, "Network Broadcast Sound Support");
diff --git a/channels/chan_oss.c b/channels/chan_oss.c
deleted file mode 100644
index 69dd71f1eea821ad2de3979eda00870a703eb0cc..0000000000000000000000000000000000000000
--- a/channels/chan_oss.c
+++ /dev/null
@@ -1,1529 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2007, Digium, Inc.
- *
- * Mark Spencer <markster@digium.com>
- *
- * FreeBSD changes and multiple device support by Luigi Rizzo, 2005.05.25
- * note-this code best seen with ts=8 (8-spaces tabs) in the editor
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-// #define HAVE_VIDEO_CONSOLE	// uncomment to enable video
-/*! \file
- *
- * \brief Channel driver for OSS sound cards
- *
- * \author Mark Spencer <markster@digium.com>
- * \author Luigi Rizzo
- *
- * \ingroup channel_drivers
- */
-
-/*! \li \ref chan_oss.c uses the configuration file \ref oss.conf
- * \addtogroup configuration_file
- */
-
-/*! \page oss.conf oss.conf
- * \verbinclude oss.conf.sample
- */
-
-/*** MODULEINFO
-	<depend>oss</depend>
-	<support_level>deprecated</support_level>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include "asterisk.h"
-
-#include <ctype.h>		/* isalnum() used here */
-#include <math.h>
-#include <sys/ioctl.h>
-
-#ifdef __linux
-#include <linux/soundcard.h>
-#elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__CYGWIN__) || defined(__GLIBC__) || defined(__sun)
-#include <sys/soundcard.h>
-#else
-#include <soundcard.h>
-#endif
-
-#include "asterisk/channel.h"
-#include "asterisk/file.h"
-#include "asterisk/callerid.h"
-#include "asterisk/module.h"
-#include "asterisk/pbx.h"
-#include "asterisk/cli.h"
-#include "asterisk/causes.h"
-#include "asterisk/musiconhold.h"
-#include "asterisk/app.h"
-#include "asterisk/bridge.h"
-#include "asterisk/format_cache.h"
-
-#include "console_video.h"
-
-/*! Global jitterbuffer configuration - by default, jb is disabled
- *  \note Values shown here match the defaults shown in oss.conf.sample */
-static struct ast_jb_conf default_jbconf =
-{
-	.flags = 0,
-	.max_size = 200,
-	.resync_threshold = 1000,
-	.impl = "fixed",
-	.target_extra = 40,
-};
-static struct ast_jb_conf global_jbconf;
-
-/*
- * Basic mode of operation:
- *
- * we have one keyboard (which receives commands from the keyboard)
- * and multiple headset's connected to audio cards.
- * Cards/Headsets are named as the sections of oss.conf.
- * The section called [general] contains the default parameters.
- *
- * At any time, the keyboard is attached to one card, and you
- * can switch among them using the command 'console foo'
- * where 'foo' is the name of the card you want.
- *
- * oss.conf parameters are
-START_CONFIG
-
-[general]
-    ; General config options, with default values shown.
-    ; You should use one section per device, with [general] being used
-    ; for the first device and also as a template for other devices.
-    ;
-    ; All but 'debug' can go also in the device-specific sections.
-    ;
-    ; debug = 0x0		; misc debug flags, default is 0
-
-    ; Set the device to use for I/O
-    ; device = /dev/dsp
-
-    ; Optional mixer command to run upon startup (e.g. to set
-    ; volume levels, mutes, etc.
-    ; mixer =
-
-    ; Software mic volume booster (or attenuator), useful for sound
-    ; cards or microphones with poor sensitivity. The volume level
-    ; is in dB, ranging from -20.0 to +20.0
-    ; boost = n			; mic volume boost in dB
-
-    ; Set the callerid for outgoing calls
-    ; callerid = John Doe <555-1234>
-
-    ; autoanswer = no		; no autoanswer on call
-    ; autohangup = yes		; hangup when other party closes
-    ; extension = s		; default extension to call
-    ; context = default		; default context for outgoing calls
-    ; language = ""		; default language
-
-    ; Default Music on Hold class to use when this channel is placed on hold in
-    ; the case that the music class is not set on the channel with
-    ; Set(CHANNEL(musicclass)=whatever) in the dialplan and the peer channel
-    ; putting this one on hold did not suggest a class to use.
-    ;
-    ; mohinterpret=default
-
-    ; If you set overridecontext to 'yes', then the whole dial string
-    ; will be interpreted as an extension, which is extremely useful
-    ; to dial SIP, IAX and other extensions which use the '@' character.
-    ; The default is 'no' just for backward compatibility, but the
-    ; suggestion is to change it.
-    ; overridecontext = no	; if 'no', the last @ will start the context
-				; if 'yes' the whole string is an extension.
-
-    ; low level device parameters in case you have problems with the
-    ; device driver on your operating system. You should not touch these
-    ; unless you know what you are doing.
-    ; queuesize = 10		; frames in device driver
-    ; frags = 8			; argument to SETFRAGMENT
-
-    ;------------------------------ JITTER BUFFER CONFIGURATION --------------------------
-    ; jbenable = yes              ; Enables the use of a jitterbuffer on the receiving side of an
-                                  ; OSS channel. Defaults to "no". An enabled jitterbuffer will
-                                  ; be used only if the sending side can create and the receiving
-                                  ; side can not accept jitter. The OSS channel can't accept jitter,
-                                  ; thus an enabled jitterbuffer on the receive OSS side will always
-                                  ; be used if the sending side can create jitter.
-
-    ; jbmaxsize = 200             ; Max length of the jitterbuffer in milliseconds.
-
-    ; jbresyncthreshold = 1000    ; Jump in the frame timestamps over which the jitterbuffer is
-                                  ; resynchronized. Useful to improve the quality of the voice, with
-                                  ; big jumps in/broken timestamps, usualy sent from exotic devices
-                                  ; and programs. Defaults to 1000.
-
-    ; jbimpl = fixed              ; Jitterbuffer implementation, used on the receiving side of an OSS
-                                  ; channel. Two implementations are currenlty available - "fixed"
-                                  ; (with size always equals to jbmax-size) and "adaptive" (with
-                                  ; variable size, actually the new jb of IAX2). Defaults to fixed.
-
-    ; jblog = no                  ; Enables jitterbuffer frame logging. Defaults to "no".
-    ;-----------------------------------------------------------------------------------
-
-[card1]
-    ; device = /dev/dsp1	; alternate device
-
-END_CONFIG
-
-.. and so on for the other cards.
-
- */
-
-/*
- * The following parameters are used in the driver:
- *
- *  FRAME_SIZE	the size of an audio frame, in samples.
- *		160 is used almost universally, so you should not change it.
- *
- *  FRAGS	the argument for the SETFRAGMENT ioctl.
- *		Overridden by the 'frags' parameter in oss.conf
- *
- *		Bits 0-7 are the base-2 log of the device's block size,
- *		bits 16-31 are the number of blocks in the driver's queue.
- *		There are a lot of differences in the way this parameter
- *		is supported by different drivers, so you may need to
- *		experiment a bit with the value.
- *		A good default for linux is 30 blocks of 64 bytes, which
- *		results in 6 frames of 320 bytes (160 samples).
- *		FreeBSD works decently with blocks of 256 or 512 bytes,
- *		leaving the number unspecified.
- *		Note that this only refers to the device buffer size,
- *		this module will then try to keep the lenght of audio
- *		buffered within small constraints.
- *
- *  QUEUE_SIZE	The max number of blocks actually allowed in the device
- *		driver's buffer, irrespective of the available number.
- *		Overridden by the 'queuesize' parameter in oss.conf
- *
- *		Should be >=2, and at most as large as the hw queue above
- *		(otherwise it will never be full).
- */
-
-#define FRAME_SIZE	160
-#define	QUEUE_SIZE	10
-
-#if defined(__FreeBSD__)
-#define	FRAGS	0x8
-#else
-#define	FRAGS	( ( (6 * 5) << 16 ) | 0x6 )
-#endif
-
-/*
- * XXX text message sizes are probably 256 chars, but i am
- * not sure if there is a suitable definition anywhere.
- */
-#define TEXT_SIZE	256
-
-#if 0
-#define	TRYOPEN	1				/* try to open on startup */
-#endif
-#define	O_CLOSE	0x444			/* special 'close' mode for device */
-/* Which device to use */
-#if defined( __OpenBSD__ ) || defined( __NetBSD__ )
-#define DEV_DSP "/dev/audio"
-#else
-#define DEV_DSP "/dev/dsp"
-#endif
-
-static char *config = "oss.conf";	/* default config file */
-
-static int oss_debug;
-
-/*!
- * \brief descriptor for one of our channels.
- *
- * There is one used for 'default' values (from the [general] entry in
- * the configuration file), and then one instance for each device
- * (the default is cloned from [general], others are only created
- * if the relevant section exists).
- */
-struct chan_oss_pvt {
-	struct chan_oss_pvt *next;
-
-	char *name;
-	int total_blocks;			/*!< total blocks in the output device */
-	int sounddev;
-	enum {
-		CHAN_OSS_DUPLEX_UNSET,
-		CHAN_OSS_DUPLEX_FULL,
-		CHAN_OSS_DUPLEX_READ,
-		CHAN_OSS_DUPLEX_WRITE
-	} duplex;
-	int autoanswer;             /*!< Boolean: whether to answer the immediately upon calling */
-	int autohangup;             /*!< Boolean: whether to hangup the call when the remote end hangs up */
-	int hookstate;              /*!< Boolean: 1 if offhook; 0 if onhook */
-	char *mixer_cmd;			/*!< initial command to issue to the mixer */
-	unsigned int queuesize;		/*!< max fragments in queue */
-	unsigned int frags;			/*!< parameter for SETFRAGMENT */
-
-	int warned;					/*!< various flags used for warnings */
-#define WARN_used_blocks	1
-#define WARN_speed		2
-#define WARN_frag		4
-	int w_errors;				/*!< overfull in the write path */
-	struct timeval lastopen;
-
-	int overridecontext;
-	int mute;
-
-	/*! boost support. BOOST_SCALE * 10 ^(BOOST_MAX/20) must
-	 *  be representable in 16 bits to avoid overflows.
-	 */
-#define	BOOST_SCALE	(1<<9)
-#define	BOOST_MAX	40			/*!< slightly less than 7 bits */
-	int boost;					/*!< input boost, scaled by BOOST_SCALE */
-	char device[64];			/*!< device to open */
-
-	pthread_t sthread;
-
-	struct ast_channel *owner;
-
-	struct video_desc *env;			/*!< parameters for video support */
-
-	char ext[AST_MAX_EXTENSION];
-	char ctx[AST_MAX_CONTEXT];
-	char language[MAX_LANGUAGE];
-	char cid_name[256];         /*!< Initial CallerID name */
-	char cid_num[256];          /*!< Initial CallerID number  */
-	char mohinterpret[MAX_MUSICCLASS];
-
-	/*! buffers used in oss_write */
-	char oss_write_buf[FRAME_SIZE * 2];
-	int oss_write_dst;
-	/*! buffers used in oss_read - AST_FRIENDLY_OFFSET space for headers
-	 *  plus enough room for a full frame
-	 */
-	char oss_read_buf[FRAME_SIZE * 2 + AST_FRIENDLY_OFFSET];
-	int readpos;				/*!< read position above */
-	struct ast_frame read_f;	/*!< returned by oss_read */
-};
-
-/*! forward declaration */
-static struct chan_oss_pvt *find_desc(const char *dev);
-
-static char *oss_active;	 /*!< the active device */
-
-/*! \brief return the pointer to the video descriptor */
-struct video_desc *get_video_desc(struct ast_channel *c)
-{
-	struct chan_oss_pvt *o = c ? ast_channel_tech_pvt(c) : find_desc(oss_active);
-	return o ? o->env : NULL;
-}
-static struct chan_oss_pvt oss_default = {
-	.sounddev = -1,
-	.duplex = CHAN_OSS_DUPLEX_UNSET, /* XXX check this */
-	.autoanswer = 1,
-	.autohangup = 1,
-	.queuesize = QUEUE_SIZE,
-	.frags = FRAGS,
-	.ext = "s",
-	.ctx = "default",
-	.readpos = AST_FRIENDLY_OFFSET,	/* start here on reads */
-	.lastopen = { 0, 0 },
-	.boost = BOOST_SCALE,
-};
-
-
-static int setformat(struct chan_oss_pvt *o, int mode);
-
-static struct ast_channel *oss_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor,
-									   const char *data, int *cause);
-static int oss_digit_begin(struct ast_channel *c, char digit);
-static int oss_digit_end(struct ast_channel *c, char digit, unsigned int duration);
-static int oss_text(struct ast_channel *c, const char *text);
-static int oss_hangup(struct ast_channel *c);
-static int oss_answer(struct ast_channel *c);
-static struct ast_frame *oss_read(struct ast_channel *chan);
-static int oss_call(struct ast_channel *c, const char *dest, int timeout);
-static int oss_write(struct ast_channel *chan, struct ast_frame *f);
-static int oss_indicate(struct ast_channel *chan, int cond, const void *data, size_t datalen);
-static int oss_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
-static char tdesc[] = "OSS Console Channel Driver";
-
-/* cannot do const because need to update some fields at runtime */
-static struct ast_channel_tech oss_tech = {
-	.type = "Console",
-	.description = tdesc,
-	.requester = oss_request,
-	.send_digit_begin = oss_digit_begin,
-	.send_digit_end = oss_digit_end,
-	.send_text = oss_text,
-	.hangup = oss_hangup,
-	.answer = oss_answer,
-	.read = oss_read,
-	.call = oss_call,
-	.write = oss_write,
-	.write_video = console_write_video,
-	.indicate = oss_indicate,
-	.fixup = oss_fixup,
-};
-
-/*!
- * \brief returns a pointer to the descriptor with the given name
- */
-static struct chan_oss_pvt *find_desc(const char *dev)
-{
-	struct chan_oss_pvt *o = NULL;
-
-	if (!dev)
-		ast_log(LOG_WARNING, "null dev\n");
-
-	for (o = oss_default.next; o && o->name && dev && strcmp(o->name, dev) != 0; o = o->next);
-
-	if (!o)
-		ast_log(LOG_WARNING, "could not find <%s>\n", dev ? dev : "--no-device--");
-
-	return o;
-}
-
-/* !
- * \brief split a string in extension-context, returns pointers to malloc'ed
- *        strings.
- *
- * If we do not have 'overridecontext' then the last @ is considered as
- * a context separator, and the context is overridden.
- * This is usually not very necessary as you can play with the dialplan,
- * and it is nice not to need it because you have '@' in SIP addresses.
- *
- * \return the buffer address.
- */
-static char *ast_ext_ctx(const char *src, char **ext, char **ctx)
-{
-	struct chan_oss_pvt *o = find_desc(oss_active);
-
-	if (ext == NULL || ctx == NULL)
-		return NULL;			/* error */
-
-	*ext = *ctx = NULL;
-
-	if (src && *src != '\0')
-		*ext = ast_strdup(src);
-
-	if (*ext == NULL)
-		return NULL;
-
-	if (!o->overridecontext) {
-		/* parse from the right */
-		*ctx = strrchr(*ext, '@');
-		if (*ctx)
-			*(*ctx)++ = '\0';
-	}
-
-	return *ext;
-}
-
-/*!
- * \brief Returns the number of blocks used in the audio output channel
- */
-static int used_blocks(struct chan_oss_pvt *o)
-{
-	struct audio_buf_info info;
-
-	if (ioctl(o->sounddev, SNDCTL_DSP_GETOSPACE, &info)) {
-		if (!(o->warned & WARN_used_blocks)) {
-			ast_log(LOG_WARNING, "Error reading output space\n");
-			o->warned |= WARN_used_blocks;
-		}
-		return 1;
-	}
-
-	if (o->total_blocks == 0) {
-		if (0)					/* debugging */
-			ast_log(LOG_WARNING, "fragtotal %d size %d avail %d\n", info.fragstotal, info.fragsize, info.fragments);
-		o->total_blocks = info.fragments;
-	}
-
-	return o->total_blocks - info.fragments;
-}
-
-/*! Write an exactly FRAME_SIZE sized frame */
-static int soundcard_writeframe(struct chan_oss_pvt *o, short *data)
-{
-	int res;
-
-	if (o->sounddev < 0)
-		setformat(o, O_RDWR);
-	if (o->sounddev < 0)
-		return 0;				/* not fatal */
-	/*
-	 * Nothing complex to manage the audio device queue.
-	 * If the buffer is full just drop the extra, otherwise write.
-	 * XXX in some cases it might be useful to write anyways after
-	 * a number of failures, to restart the output chain.
-	 */
-	res = used_blocks(o);
-	if (res > o->queuesize) {	/* no room to write a block */
-		if (o->w_errors++ == 0 && (oss_debug & 0x4))
-			ast_log(LOG_WARNING, "write: used %d blocks (%d)\n", res, o->w_errors);
-		return 0;
-	}
-	o->w_errors = 0;
-	return write(o->sounddev, (void *)data, FRAME_SIZE * 2);
-}
-
-/*!
- * reset and close the device if opened,
- * then open and initialize it in the desired mode,
- * trigger reads and writes so we can start using it.
- */
-static int setformat(struct chan_oss_pvt *o, int mode)
-{
-	int fmt, desired, res, fd;
-
-	if (o->sounddev >= 0) {
-		ioctl(o->sounddev, SNDCTL_DSP_RESET, 0);
-		close(o->sounddev);
-		o->duplex = CHAN_OSS_DUPLEX_UNSET;
-		o->sounddev = -1;
-	}
-	if (mode == O_CLOSE)		/* we are done */
-		return 0;
-	if (ast_tvdiff_ms(ast_tvnow(), o->lastopen) < 1000)
-		return -1;				/* don't open too often */
-	o->lastopen = ast_tvnow();
-	fd = o->sounddev = open(o->device, mode | O_NONBLOCK);
-	if (fd < 0) {
-		ast_log(LOG_WARNING, "Unable to re-open DSP device %s: %s\n", o->device, strerror(errno));
-		return -1;
-	}
-	if (o->owner)
-		ast_channel_set_fd(o->owner, 0, fd);
-
-#if __BYTE_ORDER == __LITTLE_ENDIAN
-	fmt = AFMT_S16_LE;
-#else
-	fmt = AFMT_S16_BE;
-#endif
-	res = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt);
-	if (res < 0) {
-		ast_log(LOG_WARNING, "Unable to set format to 16-bit signed\n");
-		return -1;
-	}
-	switch (mode) {
-	case O_RDWR:
-		res = ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0);
-		/* Check to see if duplex set (FreeBSD Bug) */
-		res = ioctl(fd, SNDCTL_DSP_GETCAPS, &fmt);
-		if (res == 0 && (fmt & DSP_CAP_DUPLEX)) {
-			ast_verb(2, "Console is full duplex\n");
-			o->duplex = CHAN_OSS_DUPLEX_FULL;
-		};
-		break;
-
-	case O_WRONLY:
-		o->duplex = CHAN_OSS_DUPLEX_WRITE;
-		break;
-
-	case O_RDONLY:
-		o->duplex = CHAN_OSS_DUPLEX_READ;
-		break;
-	}
-
-	fmt = 0;
-	res = ioctl(fd, SNDCTL_DSP_STEREO, &fmt);
-	if (res < 0) {
-		ast_log(LOG_WARNING, "Failed to set audio device to mono\n");
-		return -1;
-	}
-	fmt = desired = DEFAULT_SAMPLE_RATE;	/* 8000 Hz desired */
-	res = ioctl(fd, SNDCTL_DSP_SPEED, &fmt);
-
-	if (res < 0) {
-		ast_log(LOG_WARNING, "Failed to set sample rate to %d\n", desired);
-		return -1;
-	}
-	if (fmt != desired) {
-		if (!(o->warned & WARN_speed)) {
-			ast_log(LOG_WARNING,
-			    "Requested %d Hz, got %d Hz -- sound may be choppy\n",
-			    desired, fmt);
-			o->warned |= WARN_speed;
-		}
-	}
-	/*
-	 * on Freebsd, SETFRAGMENT does not work very well on some cards.
-	 * Default to use 256 bytes, let the user override
-	 */
-	if (o->frags) {
-		fmt = o->frags;
-		res = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &fmt);
-		if (res < 0) {
-			if (!(o->warned & WARN_frag)) {
-				ast_log(LOG_WARNING,
-					"Unable to set fragment size -- sound may be choppy\n");
-				o->warned |= WARN_frag;
-			}
-		}
-	}
-	/* on some cards, we need SNDCTL_DSP_SETTRIGGER to start outputting */
-	res = PCM_ENABLE_INPUT | PCM_ENABLE_OUTPUT;
-	res = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &res);
-	/* it may fail if we are in half duplex, never mind */
-	return 0;
-}
-
-/*
- * some of the standard methods supported by channels.
- */
-static int oss_digit_begin(struct ast_channel *c, char digit)
-{
-	return 0;
-}
-
-static int oss_digit_end(struct ast_channel *c, char digit, unsigned int duration)
-{
-	/* no better use for received digits than print them */
-	ast_verbose(" << Console Received digit %c of duration %u ms >> \n",
-		digit, duration);
-	return 0;
-}
-
-static int oss_text(struct ast_channel *c, const char *text)
-{
-	/* print received messages */
-	ast_verbose(" << Console Received text %s >> \n", text);
-	return 0;
-}
-
-/*!
- * \brief handler for incoming calls. Either autoanswer, or start ringing
- */
-static int oss_call(struct ast_channel *c, const char *dest, int timeout)
-{
-	struct chan_oss_pvt *o = ast_channel_tech_pvt(c);
-	struct ast_frame f = { AST_FRAME_CONTROL, };
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(name);
-		AST_APP_ARG(flags);
-	);
-	char *parse = ast_strdupa(dest);
-
-	AST_NONSTANDARD_APP_ARGS(args, parse, '/');
-
-	ast_verbose(" << Call to device '%s' dnid '%s' rdnis '%s' on console from '%s' <%s> >>\n",
-		dest,
-		S_OR(ast_channel_dialed(c)->number.str, ""),
-		S_COR(ast_channel_redirecting(c)->from.number.valid, ast_channel_redirecting(c)->from.number.str, ""),
-		S_COR(ast_channel_caller(c)->id.name.valid, ast_channel_caller(c)->id.name.str, ""),
-		S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, ""));
-	if (!ast_strlen_zero(args.flags) && strcasecmp(args.flags, "answer") == 0) {
-		f.subclass.integer = AST_CONTROL_ANSWER;
-		ast_queue_frame(c, &f);
-	} else if (!ast_strlen_zero(args.flags) && strcasecmp(args.flags, "noanswer") == 0) {
-		f.subclass.integer = AST_CONTROL_RINGING;
-		ast_queue_frame(c, &f);
-		ast_indicate(c, AST_CONTROL_RINGING);
-	} else if (o->autoanswer) {
-		ast_verbose(" << Auto-answered >> \n");
-		f.subclass.integer = AST_CONTROL_ANSWER;
-		ast_queue_frame(c, &f);
-		o->hookstate = 1;
-	} else {
-		ast_verbose("<< Type 'answer' to answer, or use 'autoanswer' for future calls >> \n");
-		f.subclass.integer = AST_CONTROL_RINGING;
-		ast_queue_frame(c, &f);
-		ast_indicate(c, AST_CONTROL_RINGING);
-	}
-	return 0;
-}
-
-/*!
- * \brief remote side answered the phone
- */
-static int oss_answer(struct ast_channel *c)
-{
-	struct chan_oss_pvt *o = ast_channel_tech_pvt(c);
-	ast_verbose(" << Console call has been answered >> \n");
-	ast_setstate(c, AST_STATE_UP);
-	o->hookstate = 1;
-	return 0;
-}
-
-static int oss_hangup(struct ast_channel *c)
-{
-	struct chan_oss_pvt *o = ast_channel_tech_pvt(c);
-
-	ast_channel_tech_pvt_set(c, NULL);
-	o->owner = NULL;
-	ast_verbose(" << Hangup on console >> \n");
-	console_video_uninit(o->env);
-	ast_module_unref(ast_module_info->self);
-	if (o->hookstate) {
-		if (o->autoanswer || o->autohangup) {
-			/* Assume auto-hangup too */
-			o->hookstate = 0;
-			setformat(o, O_CLOSE);
-		}
-	}
-	return 0;
-}
-
-/*! \brief used for data coming from the network */
-static int oss_write(struct ast_channel *c, struct ast_frame *f)
-{
-	int src;
-	struct chan_oss_pvt *o = ast_channel_tech_pvt(c);
-
-	/*
-	 * we could receive a block which is not a multiple of our
-	 * FRAME_SIZE, so buffer it locally and write to the device
-	 * in FRAME_SIZE chunks.
-	 * Keep the residue stored for future use.
-	 */
-	src = 0;					/* read position into f->data */
-	while (src < f->datalen) {
-		/* Compute spare room in the buffer */
-		int l = sizeof(o->oss_write_buf) - o->oss_write_dst;
-
-		if (f->datalen - src >= l) {	/* enough to fill a frame */
-			memcpy(o->oss_write_buf + o->oss_write_dst, f->data.ptr + src, l);
-			soundcard_writeframe(o, (short *) o->oss_write_buf);
-			src += l;
-			o->oss_write_dst = 0;
-		} else {				/* copy residue */
-			l = f->datalen - src;
-			memcpy(o->oss_write_buf + o->oss_write_dst, f->data.ptr + src, l);
-			src += l;			/* but really, we are done */
-			o->oss_write_dst += l;
-		}
-	}
-	return 0;
-}
-
-static struct ast_frame *oss_read(struct ast_channel *c)
-{
-	int res;
-	struct chan_oss_pvt *o = ast_channel_tech_pvt(c);
-	struct ast_frame *f = &o->read_f;
-
-	/* XXX can be simplified returning &ast_null_frame */
-	/* prepare a NULL frame in case we don't have enough data to return */
-	memset(f, '\0', sizeof(struct ast_frame));
-	f->frametype = AST_FRAME_NULL;
-	f->src = oss_tech.type;
-
-	res = read(o->sounddev, o->oss_read_buf + o->readpos, sizeof(o->oss_read_buf) - o->readpos);
-	if (res < 0)				/* audio data not ready, return a NULL frame */
-		return f;
-
-	o->readpos += res;
-	if (o->readpos < sizeof(o->oss_read_buf))	/* not enough samples */
-		return f;
-
-	if (o->mute)
-		return f;
-
-	o->readpos = AST_FRIENDLY_OFFSET;	/* reset read pointer for next frame */
-	if (ast_channel_state(c) != AST_STATE_UP)	/* drop data if frame is not up */
-		return f;
-	/* ok we can build and deliver the frame to the caller */
-	f->frametype = AST_FRAME_VOICE;
-	f->subclass.format = ast_format_slin;
-	f->samples = FRAME_SIZE;
-	f->datalen = FRAME_SIZE * 2;
-	f->data.ptr = o->oss_read_buf + AST_FRIENDLY_OFFSET;
-	if (o->boost != BOOST_SCALE) {	/* scale and clip values */
-		int i, x;
-		int16_t *p = (int16_t *) f->data.ptr;
-		for (i = 0; i < f->samples; i++) {
-			x = (p[i] * o->boost) / BOOST_SCALE;
-			if (x > 32767)
-				x = 32767;
-			else if (x < -32768)
-				x = -32768;
-			p[i] = x;
-		}
-	}
-
-	f->offset = AST_FRIENDLY_OFFSET;
-	return f;
-}
-
-static int oss_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
-{
-	struct chan_oss_pvt *o = ast_channel_tech_pvt(newchan);
-	o->owner = newchan;
-	return 0;
-}
-
-static int oss_indicate(struct ast_channel *c, int cond, const void *data, size_t datalen)
-{
-	struct chan_oss_pvt *o = ast_channel_tech_pvt(c);
-	int res = 0;
-
-	switch (cond) {
-	case AST_CONTROL_INCOMPLETE:
-	case AST_CONTROL_BUSY:
-	case AST_CONTROL_CONGESTION:
-	case AST_CONTROL_RINGING:
-	case AST_CONTROL_PVT_CAUSE_CODE:
-	case -1:
-		res = -1;
-		break;
-	case AST_CONTROL_PROGRESS:
-	case AST_CONTROL_PROCEEDING:
-	case AST_CONTROL_VIDUPDATE:
-	case AST_CONTROL_SRCUPDATE:
-		break;
-	case AST_CONTROL_HOLD:
-		ast_verbose(" << Console Has Been Placed on Hold >> \n");
-		ast_moh_start(c, data, o->mohinterpret);
-		break;
-	case AST_CONTROL_UNHOLD:
-		ast_verbose(" << Console Has Been Retrieved from Hold >> \n");
-		ast_moh_stop(c);
-		break;
-	default:
-		ast_log(LOG_WARNING, "Don't know how to display condition %d on %s\n", cond, ast_channel_name(c));
-		return -1;
-	}
-
-	return res;
-}
-
-/*!
- * \brief allocate a new channel.
- */
-static struct ast_channel *oss_new(struct chan_oss_pvt *o, char *ext, char *ctx, int state, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor)
-{
-	struct ast_channel *c;
-
-	c = ast_channel_alloc(1, state, o->cid_num, o->cid_name, "", ext, ctx, assignedids, requestor, 0, "Console/%s", o->device + 5);
-	if (c == NULL)
-		return NULL;
-	ast_channel_tech_set(c, &oss_tech);
-	if (o->sounddev < 0)
-		setformat(o, O_RDWR);
-	ast_channel_set_fd(c, 0, o->sounddev); /* -1 if device closed, override later */
-
-	ast_channel_set_readformat(c, ast_format_slin);
-	ast_channel_set_writeformat(c, ast_format_slin);
-	ast_channel_nativeformats_set(c, oss_tech.capabilities);
-
-	/* if the console makes the call, add video to the offer */
-	/* if (state == AST_STATE_RINGING) TODO XXX CONSOLE VIDEO IS DISABLED UNTIL IT GETS A MAINTAINER
-		c->nativeformats |= console_video_formats; */
-
-	ast_channel_tech_pvt_set(c, o);
-
-	if (!ast_strlen_zero(o->language))
-		ast_channel_language_set(c, o->language);
-	/* Don't use ast_set_callerid() here because it will
-	 * generate a needless NewCallerID event */
-	if (!ast_strlen_zero(o->cid_num)) {
-		ast_channel_caller(c)->ani.number.valid = 1;
-		ast_channel_caller(c)->ani.number.str = ast_strdup(o->cid_num);
-	}
-	if (!ast_strlen_zero(ext)) {
-		ast_channel_dialed(c)->number.str = ast_strdup(ext);
-	}
-
-	o->owner = c;
-	ast_module_ref(ast_module_info->self);
-	ast_jb_configure(c, &global_jbconf);
-	ast_channel_unlock(c);
-	if (state != AST_STATE_DOWN) {
-		if (ast_pbx_start(c)) {
-			ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(c));
-			ast_hangup(c);
-			o->owner = c = NULL;
-		}
-	}
-	console_video_start(get_video_desc(c), c); /* XXX cleanup */
-
-	return c;
-}
-
-static struct ast_channel *oss_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
-{
-	struct ast_channel *c;
-	struct chan_oss_pvt *o;
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(name);
-		AST_APP_ARG(flags);
-	);
-	char *parse = ast_strdupa(data);
-
-	AST_NONSTANDARD_APP_ARGS(args, parse, '/');
-	o = find_desc(args.name);
-
-	ast_log(LOG_WARNING, "oss_request ty <%s> data 0x%p <%s>\n", type, data, data);
-	if (o == NULL) {
-		ast_log(LOG_NOTICE, "Device %s not found\n", args.name);
-		/* XXX we could default to 'dsp' perhaps ? */
-		return NULL;
-	}
-	if (ast_format_cap_iscompatible_format(cap, ast_format_slin) == AST_FORMAT_CMP_NOT_EQUAL) {
-		struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
-		ast_log(LOG_NOTICE, "Format %s unsupported\n", ast_format_cap_get_names(cap, &codec_buf));
-		return NULL;
-	}
-	if (o->owner) {
-		ast_log(LOG_NOTICE, "Already have a call (chan %p) on the OSS channel\n", o->owner);
-		*cause = AST_CAUSE_BUSY;
-		return NULL;
-	}
-	c = oss_new(o, NULL, NULL, AST_STATE_DOWN, assignedids, requestor);
-	if (c == NULL) {
-		ast_log(LOG_WARNING, "Unable to create new OSS channel\n");
-		return NULL;
-	}
-	return c;
-}
-
-static void store_config_core(struct chan_oss_pvt *o, const char *var, const char *value);
-
-/*! Generic console command handler. Basically a wrapper for a subset
- *  of config file options which are also available from the CLI
- */
-static char *console_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct chan_oss_pvt *o = find_desc(oss_active);
-	const char *var, *value;
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = CONSOLE_VIDEO_CMDS;
-		e->usage =
-			"Usage: " CONSOLE_VIDEO_CMDS "...\n"
-			"       Generic handler for console commands.\n";
-		return NULL;
-
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc < e->args)
-		return CLI_SHOWUSAGE;
-	if (o == NULL) {
-		ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n",
-			oss_active);
-		return CLI_FAILURE;
-	}
-	var = a->argv[e->args-1];
-	value = a->argc > e->args ? a->argv[e->args] : NULL;
-	if (value)      /* handle setting */
-		store_config_core(o, var, value);
-	if (!console_video_cli(o->env, var, a->fd))	/* print video-related values */
-		return CLI_SUCCESS;
-	/* handle other values */
-	if (!strcasecmp(var, "device")) {
-		ast_cli(a->fd, "device is [%s]\n", o->device);
-	}
-	return CLI_SUCCESS;
-}
-
-static char *console_autoanswer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct chan_oss_pvt *o = find_desc(oss_active);
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "console {set|show} autoanswer [on|off]";
-		e->usage =
-			"Usage: console {set|show} autoanswer [on|off]\n"
-			"       Enables or disables autoanswer feature.  If used without\n"
-			"       argument, displays the current on/off status of autoanswer.\n"
-			"       The default value of autoanswer is in 'oss.conf'.\n";
-		return NULL;
-
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc == e->args - 1) {
-		ast_cli(a->fd, "Auto answer is %s.\n", o->autoanswer ? "on" : "off");
-		return CLI_SUCCESS;
-	}
-	if (a->argc != e->args)
-		return CLI_SHOWUSAGE;
-	if (o == NULL) {
-		ast_log(LOG_WARNING, "Cannot find device %s (should not happen!)\n",
-		    oss_active);
-		return CLI_FAILURE;
-	}
-	if (!strcasecmp(a->argv[e->args-1], "on"))
-		o->autoanswer = 1;
-	else if (!strcasecmp(a->argv[e->args - 1], "off"))
-		o->autoanswer = 0;
-	else
-		return CLI_SHOWUSAGE;
-	return CLI_SUCCESS;
-}
-
-/*! \brief helper function for the answer key/cli command */
-static char *console_do_answer(int fd)
-{
-	struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_ANSWER } };
-	struct chan_oss_pvt *o = find_desc(oss_active);
-	if (!o->owner) {
-		if (fd > -1)
-			ast_cli(fd, "No one is calling us\n");
-		return CLI_FAILURE;
-	}
-	o->hookstate = 1;
-	ast_queue_frame(o->owner, &f);
-	return CLI_SUCCESS;
-}
-
-/*!
- * \brief answer command from the console
- */
-static char *console_answer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "console answer";
-		e->usage =
-			"Usage: console answer\n"
-			"       Answers an incoming call on the console (OSS) channel.\n";
-		return NULL;
-
-	case CLI_GENERATE:
-		return NULL;	/* no completion */
-	}
-	if (a->argc != e->args)
-		return CLI_SHOWUSAGE;
-	return console_do_answer(a->fd);
-}
-
-/*!
- * \brief Console send text CLI command
- *
- * \note concatenate all arguments into a single string. argv is NULL-terminated
- * so we can use it right away
- */
-static char *console_sendtext(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct chan_oss_pvt *o = find_desc(oss_active);
-	char buf[TEXT_SIZE];
-
-	if (cmd == CLI_INIT) {
-		e->command = "console send text";
-		e->usage =
-			"Usage: console send text <message>\n"
-			"       Sends a text message for display on the remote terminal.\n";
-		return NULL;
-	} else if (cmd == CLI_GENERATE)
-		return NULL;
-
-	if (a->argc < e->args + 1)
-		return CLI_SHOWUSAGE;
-	if (!o->owner) {
-		ast_cli(a->fd, "Not in a call\n");
-		return CLI_FAILURE;
-	}
-	ast_join(buf, sizeof(buf) - 1, a->argv + e->args);
-	if (!ast_strlen_zero(buf)) {
-		struct ast_frame f = { 0, };
-		int i = strlen(buf);
-		buf[i] = '\n';
-		f.frametype = AST_FRAME_TEXT;
-		f.subclass.integer = 0;
-		f.data.ptr = buf;
-		f.datalen = i + 1;
-		ast_queue_frame(o->owner, &f);
-	}
-	return CLI_SUCCESS;
-}
-
-static char *console_hangup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct chan_oss_pvt *o = find_desc(oss_active);
-
-	if (cmd == CLI_INIT) {
-		e->command = "console hangup";
-		e->usage =
-			"Usage: console hangup\n"
-			"       Hangs up any call currently placed on the console.\n";
-		return NULL;
-	} else if (cmd == CLI_GENERATE)
-		return NULL;
-
-	if (a->argc != e->args)
-		return CLI_SHOWUSAGE;
-	if (!o->owner && !o->hookstate) { /* XXX maybe only one ? */
-		ast_cli(a->fd, "No call to hang up\n");
-		return CLI_FAILURE;
-	}
-	o->hookstate = 0;
-	if (o->owner)
-		ast_queue_hangup_with_cause(o->owner, AST_CAUSE_NORMAL_CLEARING);
-	setformat(o, O_CLOSE);
-	return CLI_SUCCESS;
-}
-
-static char *console_flash(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_FLASH } };
-	struct chan_oss_pvt *o = find_desc(oss_active);
-
-	if (cmd == CLI_INIT) {
-		e->command = "console flash";
-		e->usage =
-			"Usage: console flash\n"
-			"       Flashes the call currently placed on the console.\n";
-		return NULL;
-	} else if (cmd == CLI_GENERATE)
-		return NULL;
-
-	if (a->argc != e->args)
-		return CLI_SHOWUSAGE;
-	if (!o->owner) {			/* XXX maybe !o->hookstate too ? */
-		ast_cli(a->fd, "No call to flash\n");
-		return CLI_FAILURE;
-	}
-	o->hookstate = 0;
-	if (o->owner)
-		ast_queue_frame(o->owner, &f);
-	return CLI_SUCCESS;
-}
-
-static char *console_dial(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	char *s = NULL;
-	char *mye = NULL, *myc = NULL;
-	struct chan_oss_pvt *o = find_desc(oss_active);
-
-	if (cmd == CLI_INIT) {
-		e->command = "console dial";
-		e->usage =
-			"Usage: console dial [extension[@context]]\n"
-			"       Dials a given extension (and context if specified)\n";
-		return NULL;
-	} else if (cmd == CLI_GENERATE)
-		return NULL;
-
-	if (a->argc > e->args + 1)
-		return CLI_SHOWUSAGE;
-	if (o->owner) {	/* already in a call */
-		int i;
-		struct ast_frame f = { AST_FRAME_DTMF, { 0 } };
-		const char *digits;
-
-		if (a->argc == e->args) {	/* argument is mandatory here */
-			ast_cli(a->fd, "Already in a call. You can only dial digits until you hangup.\n");
-			return CLI_FAILURE;
-		}
-		digits = a->argv[e->args];
-		/* send the string one char at a time */
-		for (i = 0; i < strlen(digits); i++) {
-			f.subclass.integer = digits[i];
-			ast_queue_frame(o->owner, &f);
-		}
-		return CLI_SUCCESS;
-	}
-	/* if we have an argument split it into extension and context */
-	if (a->argc == e->args + 1)
-		s = ast_ext_ctx(a->argv[e->args], &mye, &myc);
-	/* supply default values if needed */
-	if (mye == NULL)
-		mye = o->ext;
-	if (myc == NULL)
-		myc = o->ctx;
-	if (ast_exists_extension(NULL, myc, mye, 1, NULL)) {
-		o->hookstate = 1;
-		oss_new(o, mye, myc, AST_STATE_RINGING, NULL, NULL);
-	} else
-		ast_cli(a->fd, "No such extension '%s' in context '%s'\n", mye, myc);
-	if (s)
-		ast_free(s);
-	return CLI_SUCCESS;
-}
-
-static char *console_mute(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct chan_oss_pvt *o = find_desc(oss_active);
-	const char *s;
-	int toggle = 0;
-
-	if (cmd == CLI_INIT) {
-		e->command = "console {mute|unmute} [toggle]";
-		e->usage =
-			"Usage: console {mute|unmute} [toggle]\n"
-			"       Mute/unmute the microphone.\n";
-		return NULL;
-	} else if (cmd == CLI_GENERATE)
-		return NULL;
-
-	if (a->argc > e->args)
-		return CLI_SHOWUSAGE;
-	if (a->argc == e->args) {
-		if (strcasecmp(a->argv[e->args-1], "toggle"))
-			return CLI_SHOWUSAGE;
-		toggle = 1;
-	}
-	s = a->argv[e->args-2];
-	if (!strcasecmp(s, "mute"))
-		o->mute = toggle ? !o->mute : 1;
-	else if (!strcasecmp(s, "unmute"))
-		o->mute = toggle ? !o->mute : 0;
-	else
-		return CLI_SHOWUSAGE;
-	ast_cli(a->fd, "Console mic is %s\n", o->mute ? "off" : "on");
-	return CLI_SUCCESS;
-}
-
-static char *console_transfer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct chan_oss_pvt *o = find_desc(oss_active);
-	char *tmp, *ext, *ctx;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "console transfer";
-		e->usage =
-			"Usage: console transfer <extension>[@context]\n"
-			"       Transfers the currently connected call to the given extension (and\n"
-			"       context if specified)\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 3)
-		return CLI_SHOWUSAGE;
-	if (o == NULL)
-		return CLI_FAILURE;
-	if (o->owner == NULL || !ast_channel_is_bridged(o->owner)) {
-		ast_cli(a->fd, "There is no call to transfer\n");
-		return CLI_SUCCESS;
-	}
-
-	tmp = ast_ext_ctx(a->argv[2], &ext, &ctx);
-	if (ctx == NULL) {			/* supply default context if needed */
-		ctx = ast_strdupa(ast_channel_context(o->owner));
-	}
-	if (ast_bridge_transfer_blind(1, o->owner, ext, ctx, NULL, NULL) != AST_BRIDGE_TRANSFER_SUCCESS) {
-		ast_log(LOG_WARNING, "Unable to transfer call from channel %s\n", ast_channel_name(o->owner));
-	}
-	ast_free(tmp);
-	return CLI_SUCCESS;
-}
-
-static char *console_active(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "console {set|show} active [<device>]";
-		e->usage =
-			"Usage: console active [device]\n"
-			"       If used without a parameter, displays which device is the current\n"
-			"       console.  If a device is specified, the console sound device is changed to\n"
-			"       the device specified.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc == 3)
-		ast_cli(a->fd, "active console is [%s]\n", oss_active);
-	else if (a->argc != 4)
-		return CLI_SHOWUSAGE;
-	else {
-		struct chan_oss_pvt *o;
-		if (strcmp(a->argv[3], "show") == 0) {
-			for (o = oss_default.next; o; o = o->next)
-				ast_cli(a->fd, "device [%s] exists\n", o->name);
-			return CLI_SUCCESS;
-		}
-		o = find_desc(a->argv[3]);
-		if (o == NULL)
-			ast_cli(a->fd, "No device [%s] exists\n", a->argv[3]);
-		else
-			oss_active = o->name;
-	}
-	return CLI_SUCCESS;
-}
-
-/*!
- * \brief store the boost factor
- */
-static void store_boost(struct chan_oss_pvt *o, const char *s)
-{
-	double boost = 0;
-	if (sscanf(s, "%30lf", &boost) != 1) {
-		ast_log(LOG_WARNING, "invalid boost <%s>\n", s);
-		return;
-	}
-	if (boost < -BOOST_MAX) {
-		ast_log(LOG_WARNING, "boost %s too small, using %d\n", s, -BOOST_MAX);
-		boost = -BOOST_MAX;
-	} else if (boost > BOOST_MAX) {
-		ast_log(LOG_WARNING, "boost %s too large, using %d\n", s, BOOST_MAX);
-		boost = BOOST_MAX;
-	}
-	boost = exp(log(10) * boost / 20) * BOOST_SCALE;
-	o->boost = boost;
-	ast_log(LOG_WARNING, "setting boost %s to %d\n", s, o->boost);
-}
-
-static char *console_boost(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct chan_oss_pvt *o = find_desc(oss_active);
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "console boost";
-		e->usage =
-			"Usage: console boost [boost in dB]\n"
-			"       Sets or display mic boost in dB\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc == 2)
-		ast_cli(a->fd, "boost currently %5.1f\n", 20 * log10(((double) o->boost / (double) BOOST_SCALE)));
-	else if (a->argc == 3)
-		store_boost(o, a->argv[2]);
-	return CLI_SUCCESS;
-}
-
-static struct ast_cli_entry cli_oss[] = {
-	AST_CLI_DEFINE(console_answer, "Answer an incoming console call"),
-	AST_CLI_DEFINE(console_hangup, "Hangup a call on the console"),
-	AST_CLI_DEFINE(console_flash, "Flash a call on the console"),
-	AST_CLI_DEFINE(console_dial, "Dial an extension on the console"),
-	AST_CLI_DEFINE(console_mute, "Disable/Enable mic input"),
-	AST_CLI_DEFINE(console_transfer, "Transfer a call to a different extension"),
-	AST_CLI_DEFINE(console_cmd, "Generic console command"),
-	AST_CLI_DEFINE(console_sendtext, "Send text to the remote device"),
-	AST_CLI_DEFINE(console_autoanswer, "Sets/displays autoanswer"),
-	AST_CLI_DEFINE(console_boost, "Sets/displays mic boost in dB"),
-	AST_CLI_DEFINE(console_active, "Sets/displays active console"),
-};
-
-/*!
- * store the mixer argument from the config file, filtering possibly
- * invalid or dangerous values (the string is used as argument for
- * system("mixer %s")
- */
-static void store_mixer(struct chan_oss_pvt *o, const char *s)
-{
-	int i;
-
-	for (i = 0; i < strlen(s); i++) {
-		if (!isalnum(s[i]) && strchr(" \t-/", s[i]) == NULL) {
-			ast_log(LOG_WARNING, "Suspect char %c in mixer cmd, ignoring:\n\t%s\n", s[i], s);
-			return;
-		}
-	}
-	if (o->mixer_cmd)
-		ast_free(o->mixer_cmd);
-	o->mixer_cmd = ast_strdup(s);
-	ast_log(LOG_WARNING, "setting mixer %s\n", s);
-}
-
-/*!
- * store the callerid components
- */
-static void store_callerid(struct chan_oss_pvt *o, const char *s)
-{
-	ast_callerid_split(s, o->cid_name, sizeof(o->cid_name), o->cid_num, sizeof(o->cid_num));
-}
-
-static void store_config_core(struct chan_oss_pvt *o, const char *var, const char *value)
-{
-	CV_START(var, value);
-
-	/* handle jb conf */
-	if (!ast_jb_read_conf(&global_jbconf, var, value))
-		return;
-
-	if (!console_video_config(&o->env, var, value))
-		return;	/* matched there */
-	CV_BOOL("autoanswer", o->autoanswer);
-	CV_BOOL("autohangup", o->autohangup);
-	CV_BOOL("overridecontext", o->overridecontext);
-	CV_STR("device", o->device);
-	CV_UINT("frags", o->frags);
-	CV_UINT("debug", oss_debug);
-	CV_UINT("queuesize", o->queuesize);
-	CV_STR("context", o->ctx);
-	CV_STR("language", o->language);
-	CV_STR("mohinterpret", o->mohinterpret);
-	CV_STR("extension", o->ext);
-	CV_F("mixer", store_mixer(o, value));
-	CV_F("callerid", store_callerid(o, value))  ;
-	CV_F("boost", store_boost(o, value));
-
-	CV_END;
-}
-
-/*!
- * grab fields from the config file, init the descriptor and open the device.
- */
-static struct chan_oss_pvt *store_config(struct ast_config *cfg, char *ctg)
-{
-	struct ast_variable *v;
-	struct chan_oss_pvt *o;
-
-	if (ctg == NULL) {
-		o = &oss_default;
-		ctg = "general";
-	} else {
-		if (!(o = ast_calloc(1, sizeof(*o))))
-			return NULL;
-		*o = oss_default;
-		/* "general" is also the default thing */
-		if (strcmp(ctg, "general") == 0) {
-			o->name = ast_strdup("dsp");
-			oss_active = o->name;
-			goto openit;
-		}
-		o->name = ast_strdup(ctg);
-	}
-
-	strcpy(o->mohinterpret, "default");
-
-	o->lastopen = ast_tvnow();	/* don't leave it 0 or tvdiff may wrap */
-	/* fill other fields from configuration */
-	for (v = ast_variable_browse(cfg, ctg); v; v = v->next) {
-		store_config_core(o, v->name, v->value);
-	}
-	if (ast_strlen_zero(o->device))
-		ast_copy_string(o->device, DEV_DSP, sizeof(o->device));
-	if (o->mixer_cmd) {
-		char *cmd;
-
-		if (ast_asprintf(&cmd, "mixer %s", o->mixer_cmd) >= 0) {
-			ast_log(LOG_WARNING, "running [%s]\n", cmd);
-			if (system(cmd) < 0) {
-				ast_log(LOG_WARNING, "system() failed: %s\n", strerror(errno));
-			}
-			ast_free(cmd);
-		}
-	}
-
-	/* if the config file requested to start the GUI, do it */
-	if (get_gui_startup(o->env))
-		console_video_start(o->env, NULL);
-
-	if (o == &oss_default)		/* we are done with the default */
-		return NULL;
-
-openit:
-#ifdef TRYOPEN
-	if (setformat(o, O_RDWR) < 0) {	/* open device */
-		ast_verb(1, "Device %s not detected\n", ctg);
-		ast_verb(1, "Turn off OSS support by adding " "'noload=chan_oss.so' in /etc/asterisk/modules.conf\n");
-		goto error;
-	}
-	if (o->duplex != CHAN_OSS_DUPLEX_FULL)
-		ast_log(LOG_WARNING, "XXX I don't work right with non " "full-duplex sound cards XXX\n");
-#endif /* TRYOPEN */
-
-	/* link into list of devices */
-	if (o != &oss_default) {
-		o->next = oss_default.next;
-		oss_default.next = o;
-	}
-	return o;
-
-#ifdef TRYOPEN
-error:
-	if (o != &oss_default)
-		ast_free(o);
-	return NULL;
-#endif
-}
-
-static int unload_module(void)
-{
-	struct chan_oss_pvt *o, *next;
-
-	ast_channel_unregister(&oss_tech);
-	ast_cli_unregister_multiple(cli_oss, ARRAY_LEN(cli_oss));
-
-	o = oss_default.next;
-	while (o) {
-		close(o->sounddev);
-		if (o->owner)
-			ast_softhangup(o->owner, AST_SOFTHANGUP_APPUNLOAD);
-		if (o->owner)
-			return -1;
-		next = o->next;
-		ast_free(o->name);
-		ast_free(o);
-		o = next;
-	}
-	ao2_cleanup(oss_tech.capabilities);
-	oss_tech.capabilities = NULL;
-
-	return 0;
-}
-
-/*!
- * \brief Load the module
- *
- * Module loading including tests for configuration or dependencies.
- * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
- * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
- * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the
- * configuration file or other non-critical problem return
- * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
- */
-static int load_module(void)
-{
-	struct ast_config *cfg = NULL;
-	char *ctg = NULL;
-	struct ast_flags config_flags = { 0 };
-
-	/* Copy the default jb config over global_jbconf */
-	memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
-
-	/* load config file */
-	if (!(cfg = ast_config_load(config, config_flags))) {
-		ast_log(LOG_NOTICE, "Unable to load config %s\n", config);
-		return AST_MODULE_LOAD_DECLINE;
-	} else if (cfg == CONFIG_STATUS_FILEINVALID) {
-		ast_log(LOG_ERROR, "Config file %s is in an invalid format.  Aborting.\n", config);
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	do {
-		store_config(cfg, ctg);
-	} while ( (ctg = ast_category_browse(cfg, ctg)) != NULL);
-
-	ast_config_destroy(cfg);
-
-	if (find_desc(oss_active) == NULL) {
-		ast_log(LOG_NOTICE, "Device %s not found\n", oss_active);
-		/* XXX we could default to 'dsp' perhaps ? */
-		unload_module();
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	if (!(oss_tech.capabilities = ast_format_cap_alloc(0))) {
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	ast_format_cap_append(oss_tech.capabilities, ast_format_slin, 0);
-
-	/* TODO XXX CONSOLE VIDEO IS DISABLE UNTIL IT HAS A MAINTAINER
-	 * add console_video_formats to oss_tech.capabilities once this occurs. */
-
-	if (ast_channel_register(&oss_tech)) {
-		ast_log(LOG_ERROR, "Unable to register channel type 'OSS'\n");
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	ast_cli_register_multiple(cli_oss, ARRAY_LEN(cli_oss));
-
-	return AST_MODULE_LOAD_SUCCESS;
-}
-
-AST_MODULE_INFO_STANDARD_DEPRECATED(ASTERISK_GPL_KEY, "OSS Console Channel Driver");
diff --git a/channels/chan_phone.c b/channels/chan_phone.c
deleted file mode 100644
index 66c911b30183a5224dfe0ae25187a11434f4951b..0000000000000000000000000000000000000000
--- a/channels/chan_phone.c
+++ /dev/null
@@ -1,1519 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2005, Digium, Inc.
- *
- * Mark Spencer <markster@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Generic Linux Telephony Interface driver
- *
- * \author Mark Spencer <markster@digium.com>
- *
- * \ingroup channel_drivers
- */
-
-/*! \li \ref chan_phone.c uses the configuration file \ref phone.conf
- * \addtogroup configuration_file
- */
-
-/*! \page phone.conf phone.conf
- * \verbinclude phone.conf.sample
- */
-
-/*** MODULEINFO
-	<depend>ixjuser</depend>
-	<support_level>deprecated</support_level>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include "asterisk.h"
-
-#include <ctype.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <arpa/inet.h>
-#include <fcntl.h>
-#include <sys/ioctl.h>
-#include <signal.h>
-#ifdef HAVE_LINUX_COMPILER_H
-#include <linux/compiler.h>
-#endif
-#include <linux/telephony.h>
-/* Still use some IXJ specific stuff */
-#include <linux/version.h>
-#include <linux/ixjuser.h>
-
-#include "asterisk/lock.h"
-#include "asterisk/channel.h"
-#include "asterisk/config.h"
-#include "asterisk/module.h"
-#include "asterisk/pbx.h"
-#include "asterisk/utils.h"
-#include "asterisk/callerid.h"
-#include "asterisk/causes.h"
-#include "asterisk/stringfields.h"
-#include "asterisk/musiconhold.h"
-#include "asterisk/format_cache.h"
-#include "asterisk/format_compatibility.h"
-
-#include "chan_phone.h"
-
-#ifdef QTI_PHONEJACK_TJ_PCI	/* check for the newer quicknet driver v.3.1.0 which has this symbol */
-#define QNDRV_VER 310
-#else
-#define QNDRV_VER 100
-#endif
-
-#if QNDRV_VER > 100
-#ifdef __linux__
-#define IXJ_PHONE_RING_START(x)	ioctl(p->fd, PHONE_RING_START, &x);
-#else /* FreeBSD and others */
-#define IXJ_PHONE_RING_START(x)	ioctl(p->fd, PHONE_RING_START, x);
-#endif /* __linux__ */
-#else	/* older driver */
-#define IXJ_PHONE_RING_START(x)	ioctl(p->fd, PHONE_RING_START, &x);
-#endif
-
-#define DEFAULT_CALLER_ID "Unknown"
-#define PHONE_MAX_BUF 480
-#define DEFAULT_GAIN 0x100
-
-static const char tdesc[] = "Standard Linux Telephony API Driver";
-static const char config[] = "phone.conf";
-
-/* Default context for dialtone mode */
-static char context[AST_MAX_EXTENSION] = "default";
-
-/* Default language */
-static char language[MAX_LANGUAGE] = "";
-
-static int echocancel = AEC_OFF;
-
-static int silencesupression = 0;
-
-static struct ast_format_cap *prefcap;
-
-/* Protect the interface list (of phone_pvt's) */
-AST_MUTEX_DEFINE_STATIC(iflock);
-
-/* Protect the monitoring thread, so only one process can kill or start it, and not
-   when it's doing something critical. */
-AST_MUTEX_DEFINE_STATIC(monlock);
-
-/* Boolean value whether the monitoring thread shall continue. */
-static unsigned int monitor;
-
-/* This is the thread for the monitor which checks for input on the channels
-   which are not currently in use.  */
-static pthread_t monitor_thread = AST_PTHREADT_NULL;
-
-static int restart_monitor(void);
-
-/* The private structures of the Phone Jack channels are linked for
-   selecting outgoing channels */
-
-#define MODE_DIALTONE 	1
-#define MODE_IMMEDIATE	2
-#define MODE_FXO	3
-#define MODE_FXS        4
-#define MODE_SIGMA      5
-
-static struct phone_pvt {
-	int fd;							/* Raw file descriptor for this device */
-	struct ast_channel *owner;		/* Channel we belong to, possibly NULL */
-	int mode;						/* Is this in the  */
-	struct ast_format *lastformat;            /* Last output format */
-	struct ast_format *lastinput;             /* Last input format */
-	int ministate;					/* Miniature state, for dialtone mode */
-	char dev[256];					/* Device name */
-	struct phone_pvt *next;			/* Next channel in list */
-	struct ast_frame fr;			/* Frame */
-	char offset[AST_FRIENDLY_OFFSET];
-	char buf[PHONE_MAX_BUF];					/* Static buffer for reading frames */
-	int obuflen;
-	int dialtone;
-	int txgain, rxgain;             /* gain control for playing, recording  */
-									/* 0x100 - 1.0, 0x200 - 2.0, 0x80 - 0.5 */
-	int cpt;						/* Call Progress Tone playing? */
-	int silencesupression;
-	char context[AST_MAX_EXTENSION];
-	char obuf[PHONE_MAX_BUF * 2];
-	char ext[AST_MAX_EXTENSION];
-	char language[MAX_LANGUAGE];
-	char cid_num[AST_MAX_EXTENSION];
-	char cid_name[AST_MAX_EXTENSION];
-} *iflist = NULL;
-
-static char cid_num[AST_MAX_EXTENSION];
-static char cid_name[AST_MAX_EXTENSION];
-
-static struct ast_channel *phone_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause);
-static int phone_digit_begin(struct ast_channel *ast, char digit);
-static int phone_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
-static int phone_call(struct ast_channel *ast, const char *dest, int timeout);
-static int phone_hangup(struct ast_channel *ast);
-static int phone_answer(struct ast_channel *ast);
-static struct ast_frame *phone_read(struct ast_channel *ast);
-static int phone_write(struct ast_channel *ast, struct ast_frame *frame);
-static struct ast_frame *phone_exception(struct ast_channel *ast);
-static int phone_send_text(struct ast_channel *ast, const char *text);
-static int phone_fixup(struct ast_channel *old, struct ast_channel *new);
-static int phone_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen);
-
-static struct ast_channel_tech phone_tech = {
-	.type = "Phone",
-	.description = tdesc,
-	.requester = phone_request,
-	.send_digit_begin = phone_digit_begin,
-	.send_digit_end = phone_digit_end,
-	.call = phone_call,
-	.hangup = phone_hangup,
-	.answer = phone_answer,
-	.read = phone_read,
-	.write = phone_write,
-	.exception = phone_exception,
-	.indicate = phone_indicate,
-	.fixup = phone_fixup
-};
-
-static struct ast_channel_tech phone_tech_fxs = {
-	.type = "Phone",
-	.description = tdesc,
-	.requester = phone_request,
-	.send_digit_begin = phone_digit_begin,
-	.send_digit_end = phone_digit_end,
-	.call = phone_call,
-	.hangup = phone_hangup,
-	.answer = phone_answer,
-	.read = phone_read,
-	.write = phone_write,
-	.exception = phone_exception,
-	.write_video = phone_write,
-	.send_text = phone_send_text,
-	.indicate = phone_indicate,
-	.fixup = phone_fixup
-};
-
-static struct ast_channel_tech *cur_tech;
-
-static int phone_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen)
-{
-	struct phone_pvt *p = ast_channel_tech_pvt(chan);
-	int res=-1;
-	ast_debug(1, "Requested indication %d on channel %s\n", condition, ast_channel_name(chan));
-	switch(condition) {
-	case AST_CONTROL_FLASH:
-		ioctl(p->fd, IXJCTL_PSTN_SET_STATE, PSTN_ON_HOOK);
-		usleep(320000);
-		ioctl(p->fd, IXJCTL_PSTN_SET_STATE, PSTN_OFF_HOOK);
-		ao2_cleanup(p->lastformat);
-		p->lastformat = NULL;
-		res = 0;
-		break;
-	case AST_CONTROL_HOLD:
-		ast_moh_start(chan, data, NULL);
-		break;
-	case AST_CONTROL_UNHOLD:
-		ast_moh_stop(chan);
-		break;
-	case AST_CONTROL_SRCUPDATE:
-		res = 0;
-		break;
-	case AST_CONTROL_PVT_CAUSE_CODE:
-		break;
-	default:
-		ast_log(LOG_WARNING, "Condition %d is not supported on channel %s\n", condition, ast_channel_name(chan));
-	}
-	return res;
-}
-
-static int phone_fixup(struct ast_channel *old, struct ast_channel *new)
-{
-	struct phone_pvt *pvt = ast_channel_tech_pvt(old);
-	if (pvt && pvt->owner == old)
-		pvt->owner = new;
-	return 0;
-}
-
-static int phone_digit_begin(struct ast_channel *chan, char digit)
-{
-	/* XXX Modify this callback to let Asterisk support controlling the length of DTMF */
-	return 0;
-}
-
-static int phone_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
-{
-	struct phone_pvt *p;
-	int outdigit;
-	p = ast_channel_tech_pvt(ast);
-	ast_debug(1, "Dialed %c\n", digit);
-	switch(digit) {
-	case '0':
-	case '1':
-	case '2':
-	case '3':
-	case '4':
-	case '5':
-	case '6':
-	case '7':
-	case '8':
-	case '9':
-		outdigit = digit - '0';
-		break;
-	case '*':
-		outdigit = 11;
-		break;
-	case '#':
-		outdigit = 12;
-		break;
-	case 'f':	/*flash*/
-	case 'F':
-		ioctl(p->fd, IXJCTL_PSTN_SET_STATE, PSTN_ON_HOOK);
-		usleep(320000);
-		ioctl(p->fd, IXJCTL_PSTN_SET_STATE, PSTN_OFF_HOOK);
-		ao2_cleanup(p->lastformat);
-		p->lastformat = NULL;
-		return 0;
-	default:
-		ast_log(LOG_WARNING, "Unknown digit '%c'\n", digit);
-		return -1;
-	}
-	ast_debug(1, "Dialed %d\n", outdigit);
-	ioctl(p->fd, PHONE_PLAY_TONE, outdigit);
-	ao2_cleanup(p->lastformat);
-	p->lastformat = NULL;
-	return 0;
-}
-
-static int phone_call(struct ast_channel *ast, const char *dest, int timeout)
-{
-	struct phone_pvt *p;
-
-	PHONE_CID cid;
-	struct timeval UtcTime = ast_tvnow();
-	struct ast_tm tm;
-	int start;
-
-	ast_localtime(&UtcTime, &tm, NULL);
-
-	memset(&cid, 0, sizeof(PHONE_CID));
-    snprintf(cid.month, sizeof(cid.month), "%02d",(tm.tm_mon + 1));
-    snprintf(cid.day, sizeof(cid.day),     "%02d", tm.tm_mday);
-    snprintf(cid.hour, sizeof(cid.hour),   "%02d", tm.tm_hour);
-    snprintf(cid.min, sizeof(cid.min),     "%02d", tm.tm_min);
-	/* the standard format of ast->callerid is:  "name" <number>, but not always complete */
-	if (!ast_channel_connected(ast)->id.name.valid
-		|| ast_strlen_zero(ast_channel_connected(ast)->id.name.str)) {
-		strcpy(cid.name, DEFAULT_CALLER_ID);
-	} else {
-		ast_copy_string(cid.name, ast_channel_connected(ast)->id.name.str, sizeof(cid.name));
-	}
-
-	if (ast_channel_connected(ast)->id.number.valid && ast_channel_connected(ast)->id.number.str) {
-		ast_copy_string(cid.number, ast_channel_connected(ast)->id.number.str, sizeof(cid.number));
-	}
-
-	p = ast_channel_tech_pvt(ast);
-
-	if ((ast_channel_state(ast) != AST_STATE_DOWN) && (ast_channel_state(ast) != AST_STATE_RESERVED)) {
-		ast_log(LOG_WARNING, "phone_call called on %s, neither down nor reserved\n", ast_channel_name(ast));
-		return -1;
-	}
-	ast_debug(1, "Ringing %s on %s (%d)\n", dest, ast_channel_name(ast), ast_channel_fd(ast, 0));
-
-	start = IXJ_PHONE_RING_START(cid);
-	if (start == -1)
-		return -1;
-
-	if (p->mode == MODE_FXS) {
-		const char *digit = strchr(dest, '/');
-		if (digit)
-		{
-		  digit++;
-		  while (*digit)
-		    phone_digit_end(ast, *digit++, 0);
-		}
-	}
-
-  	ast_setstate(ast, AST_STATE_RINGING);
-	ast_queue_control(ast, AST_CONTROL_RINGING);
-	return 0;
-}
-
-static int phone_hangup(struct ast_channel *ast)
-{
-	struct phone_pvt *p;
-	p = ast_channel_tech_pvt(ast);
-	ast_debug(1, "phone_hangup(%s)\n", ast_channel_name(ast));
-	if (!ast_channel_tech_pvt(ast)) {
-		ast_log(LOG_WARNING, "Asked to hangup channel not connected\n");
-		return 0;
-	}
-	/* XXX Is there anything we can do to really hang up except stop recording? */
-	ast_setstate(ast, AST_STATE_DOWN);
-	if (ioctl(p->fd, PHONE_REC_STOP))
-		ast_log(LOG_WARNING, "Failed to stop recording\n");
-	if (ioctl(p->fd, PHONE_PLAY_STOP))
-		ast_log(LOG_WARNING, "Failed to stop playing\n");
-	if (ioctl(p->fd, PHONE_RING_STOP))
-		ast_log(LOG_WARNING, "Failed to stop ringing\n");
-	if (ioctl(p->fd, PHONE_CPT_STOP))
-		ast_log(LOG_WARNING, "Failed to stop sounds\n");
-
-	/* If it's an FXO, hang them up */
-	if (p->mode == MODE_FXO) {
-		if (ioctl(p->fd, PHONE_PSTN_SET_STATE, PSTN_ON_HOOK))
-			ast_debug(1, "ioctl(PHONE_PSTN_SET_STATE) failed on %s (%s)\n",ast_channel_name(ast), strerror(errno));
-	}
-
-	/* If they're off hook, give a busy signal */
-	if (ioctl(p->fd, PHONE_HOOKSTATE)) {
-		ast_debug(1, "Got hunghup, giving busy signal\n");
-		ioctl(p->fd, PHONE_BUSY);
-		p->cpt = 1;
-	}
-	ao2_cleanup(p->lastformat);
-	p->lastformat = NULL;
-	ao2_cleanup(p->lastinput);
-	p->lastinput = NULL;
-	p->ministate = 0;
-	p->obuflen = 0;
-	p->dialtone = 0;
-	memset(p->ext, 0, sizeof(p->ext));
-	((struct phone_pvt *)(ast_channel_tech_pvt(ast)))->owner = NULL;
-	ast_module_unref(ast_module_info->self);
-	ast_verb(3, "Hungup '%s'\n", ast_channel_name(ast));
-	ast_channel_tech_pvt_set(ast, NULL);
-	ast_setstate(ast, AST_STATE_DOWN);
-	restart_monitor();
-	return 0;
-}
-
-static int phone_setup(struct ast_channel *ast)
-{
-	struct phone_pvt *p;
-	p = ast_channel_tech_pvt(ast);
-	ioctl(p->fd, PHONE_CPT_STOP);
-	/* Nothing to answering really, just start recording */
-	if (ast_format_cmp(ast_channel_rawreadformat(ast), ast_format_g729) == AST_FORMAT_CMP_EQUAL) {
-		/* Prefer g729 */
-		ioctl(p->fd, PHONE_REC_STOP);
-		if (!p->lastinput || (ast_format_cmp(p->lastinput, ast_format_g729) != AST_FORMAT_CMP_EQUAL)) {
-			ao2_replace(p->lastinput, ast_format_g729);
-			if (ioctl(p->fd, PHONE_REC_CODEC, G729)) {
-				ast_log(LOG_WARNING, "Failed to set codec to g729\n");
-				return -1;
-			}
-		}
-	} else if (ast_format_cmp(ast_channel_rawreadformat(ast), ast_format_g723) == AST_FORMAT_CMP_EQUAL) {
-		ioctl(p->fd, PHONE_REC_STOP);
-		if (!p->lastinput || (ast_format_cmp(p->lastinput, ast_format_g723) != AST_FORMAT_CMP_EQUAL)) {
-			ao2_replace(p->lastinput, ast_format_g723);
-			if (ioctl(p->fd, PHONE_REC_CODEC, G723_63)) {
-				ast_log(LOG_WARNING, "Failed to set codec to g723.1\n");
-				return -1;
-			}
-		}
-	} else if (ast_format_cmp(ast_channel_rawreadformat(ast), ast_format_slin) == AST_FORMAT_CMP_EQUAL) {
-		ioctl(p->fd, PHONE_REC_STOP);
-		if (!p->lastinput || (ast_format_cmp(p->lastinput, ast_format_slin) != AST_FORMAT_CMP_EQUAL)) {
-			ao2_replace(p->lastinput, ast_format_slin);
-			if (ioctl(p->fd, PHONE_REC_CODEC, LINEAR16)) {
-				ast_log(LOG_WARNING, "Failed to set codec to signed linear 16\n");
-				return -1;
-			}
-		}
-	} else if (ast_format_cmp(ast_channel_rawreadformat(ast), ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) {
-		ioctl(p->fd, PHONE_REC_STOP);
-		if (!p->lastinput || (ast_format_cmp(p->lastinput, ast_format_ulaw) != AST_FORMAT_CMP_EQUAL)) {
-			ao2_replace(p->lastinput, ast_format_ulaw);
-			if (ioctl(p->fd, PHONE_REC_CODEC, ULAW)) {
-				ast_log(LOG_WARNING, "Failed to set codec to uLaw\n");
-				return -1;
-			}
-		}
-	} else if (p->mode == MODE_FXS) {
-		ioctl(p->fd, PHONE_REC_STOP);
-		if (!p->lastinput || (ast_format_cmp(p->lastinput, ast_channel_rawreadformat(ast)) == AST_FORMAT_CMP_NOT_EQUAL)) {
-			ao2_replace(p->lastinput, ast_channel_rawreadformat(ast));
-			if (ioctl(p->fd, PHONE_REC_CODEC, ast_channel_rawreadformat(ast))) {
-				ast_log(LOG_WARNING, "Failed to set codec to %s\n",
-					ast_format_get_name(ast_channel_rawreadformat(ast)));
-				return -1;
-			}
-		}
-	} else {
-		ast_log(LOG_WARNING, "Can't do format %s\n", ast_format_get_name(ast_channel_rawreadformat(ast)));
-		return -1;
-	}
-	if (ioctl(p->fd, PHONE_REC_START)) {
-		ast_log(LOG_WARNING, "Failed to start recording\n");
-		return -1;
-	}
-	/* set the DTMF times (the default is too short) */
-	ioctl(p->fd, PHONE_SET_TONE_ON_TIME, 300);
-	ioctl(p->fd, PHONE_SET_TONE_OFF_TIME, 200);
-	return 0;
-}
-
-static int phone_answer(struct ast_channel *ast)
-{
-	struct phone_pvt *p;
-	p = ast_channel_tech_pvt(ast);
-	/* In case it's a LineJack, take it off hook */
-	if (p->mode == MODE_FXO) {
-		if (ioctl(p->fd, PHONE_PSTN_SET_STATE, PSTN_OFF_HOOK))
-			ast_debug(1, "ioctl(PHONE_PSTN_SET_STATE) failed on %s (%s)\n", ast_channel_name(ast), strerror(errno));
-		else
-			ast_debug(1, "Took linejack off hook\n");
-	}
-	phone_setup(ast);
-	ast_debug(1, "phone_answer(%s)\n", ast_channel_name(ast));
-	ast_channel_rings_set(ast, 0);
-	ast_setstate(ast, AST_STATE_UP);
-	return 0;
-}
-
-#if 0
-static char phone_2digit(char c)
-{
-	if (c == 12)
-		return '#';
-	else if (c == 11)
-		return '*';
-	else if ((c < 10) && (c >= 0))
-		return '0' + c - 1;
-	else
-		return '?';
-}
-#endif
-
-static struct ast_frame  *phone_exception(struct ast_channel *ast)
-{
-	int res;
-	union telephony_exception phonee;
-	struct phone_pvt *p = ast_channel_tech_pvt(ast);
-	char digit;
-
-	/* Some nice norms */
-	p->fr.datalen = 0;
-	p->fr.samples = 0;
-	p->fr.data.ptr =  NULL;
-	p->fr.src = "Phone";
-	p->fr.offset = 0;
-	p->fr.mallocd=0;
-	p->fr.delivery = ast_tv(0,0);
-
-	phonee.bytes = ioctl(p->fd, PHONE_EXCEPTION);
-	if (phonee.bits.dtmf_ready)  {
-		ast_debug(1, "phone_exception(): DTMF\n");
-
-		/* We've got a digit -- Just handle this nicely and easily */
-		digit =  ioctl(p->fd, PHONE_GET_DTMF_ASCII);
-		p->fr.subclass.integer = digit;
-		p->fr.frametype = AST_FRAME_DTMF;
-		return &p->fr;
-	}
-	if (phonee.bits.hookstate) {
-		ast_debug(1, "Hookstate changed\n");
-		res = ioctl(p->fd, PHONE_HOOKSTATE);
-		/* See if we've gone on hook, if so, notify by returning NULL */
-		ast_debug(1, "New hookstate: %d\n", res);
-		if (!res && (p->mode != MODE_FXO))
-			return NULL;
-		else {
-			if (ast_channel_state(ast) == AST_STATE_RINGING) {
-				/* They've picked up the phone */
-				p->fr.frametype = AST_FRAME_CONTROL;
-				p->fr.subclass.integer = AST_CONTROL_ANSWER;
-				phone_setup(ast);
-				ast_setstate(ast, AST_STATE_UP);
-				return &p->fr;
-			}  else
-				ast_log(LOG_WARNING, "Got off hook in weird state %u\n", ast_channel_state(ast));
-		}
-	}
-#if 1
-	if (phonee.bits.pstn_ring)
-		ast_verbose("Unit is ringing\n");
-	if (phonee.bits.caller_id) {
-		ast_verbose("We have caller ID\n");
-	}
-	if (phonee.bits.pstn_wink)
-		ast_verbose("Detected Wink\n");
-#endif
-	/* Strange -- nothing there.. */
-	p->fr.frametype = AST_FRAME_NULL;
-	p->fr.subclass.integer = 0;
-	return &p->fr;
-}
-
-static struct ast_frame  *phone_read(struct ast_channel *ast)
-{
-	int res;
-	struct phone_pvt *p = ast_channel_tech_pvt(ast);
-
-
-	/* Some nice norms */
-	p->fr.datalen = 0;
-	p->fr.samples = 0;
-	p->fr.data.ptr =  NULL;
-	p->fr.src = "Phone";
-	p->fr.offset = 0;
-	p->fr.mallocd=0;
-	p->fr.delivery = ast_tv(0,0);
-
-	/* Try to read some data... */
-	CHECK_BLOCKING(ast);
-	res = read(p->fd, p->buf, PHONE_MAX_BUF);
-	ast_clear_flag(ast_channel_flags(ast), AST_FLAG_BLOCKING);
-	if (res < 0) {
-#if 0
-		if (errno == EAGAIN) {
-			ast_log(LOG_WARNING, "Null frame received\n");
-			p->fr.frametype = AST_FRAME_NULL;
-			p->fr.subclass = 0;
-			return &p->fr;
-		}
-#endif
-		ast_log(LOG_WARNING, "Error reading: %s\n", strerror(errno));
-		return NULL;
-	}
-	p->fr.data.ptr = p->buf;
-	if (p->mode != MODE_FXS)
-	switch(p->buf[0] & 0x3) {
-	case '0':
-	case '1':
-		/* Normal */
-		break;
-	case '2':
-	case '3':
-		/* VAD/CNG, only send two words */
-		res = 4;
-		break;
-	}
-	p->fr.samples = 240;
-	p->fr.datalen = res;
-	p->fr.frametype = ast_format_get_type(p->lastinput) == AST_MEDIA_TYPE_AUDIO ?
-		AST_FRAME_VOICE : ast_format_get_type(p->lastinput) == AST_MEDIA_TYPE_IMAGE ?
-		AST_FRAME_IMAGE : AST_FRAME_VIDEO;
-	p->fr.subclass.format = p->lastinput;
-	p->fr.offset = AST_FRIENDLY_OFFSET;
-	/* Byteswap from little-endian to native-endian */
-	if (ast_format_cmp(p->fr.subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL)
-		ast_frame_byteswap_le(&p->fr);
-	return &p->fr;
-}
-
-static int phone_write_buf(struct phone_pvt *p, const char *buf, int len, int frlen, int swap)
-{
-	int res;
-	/* Store as much of the buffer as we can, then write fixed frames */
-	int space = sizeof(p->obuf) - p->obuflen;
-	/* Make sure we have enough buffer space to store the frame */
-	if (space < len)
-		len = space;
-	if (swap)
-		ast_swapcopy_samples(p->obuf+p->obuflen, buf, len/2);
-	else
-		memcpy(p->obuf + p->obuflen, buf, len);
-	p->obuflen += len;
-	while(p->obuflen > frlen) {
-		res = write(p->fd, p->obuf, frlen);
-		if (res != frlen) {
-			if (res < 1) {
-/*
- * Card is in non-blocking mode now and it works well now, but there are
- * lot of messages like this. So, this message is temporarily disabled.
- */
-				return 0;
-			} else {
-				ast_log(LOG_WARNING, "Only wrote %d of %d bytes\n", res, frlen);
-			}
-		}
-		p->obuflen -= frlen;
-		/* Move memory if necessary */
-		if (p->obuflen)
-			memmove(p->obuf, p->obuf + frlen, p->obuflen);
-	}
-	return len;
-}
-
-static int phone_send_text(struct ast_channel *ast, const char *text)
-{
-    int length = strlen(text);
-    return phone_write_buf(ast_channel_tech_pvt(ast), text, length, length, 0) ==
-           length ? 0 : -1;
-}
-
-static int phone_write(struct ast_channel *ast, struct ast_frame *frame)
-{
-	struct phone_pvt *p = ast_channel_tech_pvt(ast);
-	int res;
-	int maxfr=0;
-	char *pos;
-	int sofar;
-	int expected;
-	int codecset = 0;
-	char tmpbuf[4];
-	/* Write a frame of (presumably voice) data */
-	if (frame->frametype != AST_FRAME_VOICE && p->mode != MODE_FXS) {
-		if (frame->frametype != AST_FRAME_IMAGE)
-			ast_log(LOG_WARNING, "Don't know what to do with  frame type '%u'\n", frame->frametype);
-		return 0;
-	}
-#if 0
-	/* If we're not in up mode, go into up mode now */
-	if (ast->_state != AST_STATE_UP) {
-		ast_setstate(ast, AST_STATE_UP);
-		phone_setup(ast);
-	}
-#else
-	if (ast_channel_state(ast) != AST_STATE_UP) {
-		/* Don't try tos end audio on-hook */
-		return 0;
-	}
-#endif
-	if (ast_format_cmp(frame->subclass.format, ast_format_g729) == AST_FORMAT_CMP_EQUAL) {
-		if (!p->lastformat || (ast_format_cmp(p->lastformat, ast_format_g729) != AST_FORMAT_CMP_EQUAL)) {
-			ioctl(p->fd, PHONE_PLAY_STOP);
-			ioctl(p->fd, PHONE_REC_STOP);
-			if (ioctl(p->fd, PHONE_PLAY_CODEC, G729)) {
-				ast_log(LOG_WARNING, "Unable to set G729 mode\n");
-				return -1;
-			}
-			if (ioctl(p->fd, PHONE_REC_CODEC, G729)) {
-				ast_log(LOG_WARNING, "Unable to set G729 mode\n");
-				return -1;
-			}
-			ao2_replace(p->lastformat, ast_format_g729);
-			ao2_replace(p->lastinput, ast_format_g729);
-			/* Reset output buffer */
-			p->obuflen = 0;
-			codecset = 1;
-		}
-		if (frame->datalen > 80) {
-			ast_log(LOG_WARNING, "Frame size too large for G.729 (%d bytes)\n", frame->datalen);
-			return -1;
-		}
-		maxfr = 80;
-    } else if (ast_format_cmp(frame->subclass.format, ast_format_g723) == AST_FORMAT_CMP_EQUAL) {
-		if (!p->lastformat || (ast_format_cmp(p->lastformat, ast_format_g723) != AST_FORMAT_CMP_EQUAL)) {
-			ioctl(p->fd, PHONE_PLAY_STOP);
-			ioctl(p->fd, PHONE_REC_STOP);
-			if (ioctl(p->fd, PHONE_PLAY_CODEC, G723_63)) {
-				ast_log(LOG_WARNING, "Unable to set G723.1 mode\n");
-				return -1;
-			}
-			if (ioctl(p->fd, PHONE_REC_CODEC, G723_63)) {
-				ast_log(LOG_WARNING, "Unable to set G723.1 mode\n");
-				return -1;
-			}
-			ao2_replace(p->lastformat, ast_format_g723);
-			ao2_replace(p->lastinput, ast_format_g723);
-			/* Reset output buffer */
-			p->obuflen = 0;
-			codecset = 1;
-		}
-		if (frame->datalen > 24) {
-			ast_log(LOG_WARNING, "Frame size too large for G.723.1 (%d bytes)\n", frame->datalen);
-			return -1;
-		}
-		maxfr = 24;
-	} else if (ast_format_cmp(frame->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) {
-		if (!p->lastformat || (ast_format_cmp(p->lastformat, ast_format_slin) != AST_FORMAT_CMP_EQUAL)) {
-			ioctl(p->fd, PHONE_PLAY_STOP);
-			ioctl(p->fd, PHONE_REC_STOP);
-			if (ioctl(p->fd, PHONE_PLAY_CODEC, LINEAR16)) {
-				ast_log(LOG_WARNING, "Unable to set 16-bit linear mode\n");
-				return -1;
-			}
-			if (ioctl(p->fd, PHONE_REC_CODEC, LINEAR16)) {
-				ast_log(LOG_WARNING, "Unable to set 16-bit linear mode\n");
-				return -1;
-			}
-			ao2_replace(p->lastformat, ast_format_slin);
-			ao2_replace(p->lastinput, ast_format_slin);
-			codecset = 1;
-			/* Reset output buffer */
-			p->obuflen = 0;
-		}
-		maxfr = 480;
-	} else if (ast_format_cmp(frame->subclass.format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) {
-		if (!p->lastformat || (ast_format_cmp(p->lastformat, ast_format_ulaw) != AST_FORMAT_CMP_EQUAL)) {
-			ioctl(p->fd, PHONE_PLAY_STOP);
-			ioctl(p->fd, PHONE_REC_STOP);
-			if (ioctl(p->fd, PHONE_PLAY_CODEC, ULAW)) {
-				ast_log(LOG_WARNING, "Unable to set uLaw mode\n");
-				return -1;
-			}
-			if (ioctl(p->fd, PHONE_REC_CODEC, ULAW)) {
-				ast_log(LOG_WARNING, "Unable to set uLaw mode\n");
-				return -1;
-			}
-			ao2_replace(p->lastformat, ast_format_ulaw);
-			ao2_replace(p->lastinput, ast_format_ulaw);
-			codecset = 1;
-			/* Reset output buffer */
-			p->obuflen = 0;
-		}
-		maxfr = 240;
-	} else {
-		if (!p->lastformat || (ast_format_cmp(p->lastformat, frame->subclass.format) != AST_FORMAT_CMP_EQUAL)) {
-			ioctl(p->fd, PHONE_PLAY_STOP);
-			ioctl(p->fd, PHONE_REC_STOP);
-			if (ioctl(p->fd, PHONE_PLAY_CODEC, ast_format_compatibility_format2bitfield(frame->subclass.format))) {
-				ast_log(LOG_WARNING, "Unable to set %s mode\n",
-					ast_format_get_name(frame->subclass.format));
-				return -1;
-			}
-			if (ioctl(p->fd, PHONE_REC_CODEC, ast_format_compatibility_format2bitfield(frame->subclass.format))) {
-				ast_log(LOG_WARNING, "Unable to set %s mode\n",
-					ast_format_get_name(frame->subclass.format));
-				return -1;
-			}
-			ao2_replace(p->lastformat, frame->subclass.format);
-			ao2_replace(p->lastinput, frame->subclass.format);
-			codecset = 1;
-			/* Reset output buffer */
-			p->obuflen = 0;
-		}
-		maxfr = 480;
-	}
- 	if (codecset) {
-		ioctl(p->fd, PHONE_REC_DEPTH, 3);
-		ioctl(p->fd, PHONE_PLAY_DEPTH, 3);
-		if (ioctl(p->fd, PHONE_PLAY_START)) {
-			ast_log(LOG_WARNING, "Failed to start playback\n");
-			return -1;
-		}
-		if (ioctl(p->fd, PHONE_REC_START)) {
-			ast_log(LOG_WARNING, "Failed to start recording\n");
-			return -1;
-		}
-	}
-	/* If we get here, we have a frame of Appropriate data */
-	sofar = 0;
-	pos = frame->data.ptr;
-	while(sofar < frame->datalen) {
-		/* Write in no more than maxfr sized frames */
-		expected = frame->datalen - sofar;
-		if (maxfr < expected)
-			expected = maxfr;
-		/* XXX Internet Phone Jack does not handle the 4-byte VAD frame properly! XXX
-		   we have to pad it to 24 bytes still.  */
-		if (frame->datalen == 4) {
-			if (p->silencesupression) {
-				memcpy(tmpbuf, frame->data.ptr, 4);
-				expected = 24;
-				res = phone_write_buf(p, tmpbuf, expected, maxfr, 0);
-			}
-			res = 4;
-			expected=4;
-		} else {
-			int swap = 0;
-#if __BYTE_ORDER == __BIG_ENDIAN
-			if (ast_format_cmp(frame->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL)
-				swap = 1; /* Swap big-endian samples to little-endian as we copy */
-#endif
-			res = phone_write_buf(p, pos, expected, maxfr, swap);
-		}
-		if (res != expected) {
-			if ((errno != EAGAIN) && (errno != EINTR)) {
-				if (res < 0)
-					ast_log(LOG_WARNING, "Write returned error (%s)\n", strerror(errno));
-	/*
-	 * Card is in non-blocking mode now and it works well now, but there are
-	 * lot of messages like this. So, this message is temporarily disabled.
-	 */
-#if 0
-				else
-					ast_log(LOG_WARNING, "Only wrote %d of %d bytes\n", res, frame->datalen);
-#endif
-				return -1;
-			} else /* Pretend it worked */
-				res = expected;
-		}
-		sofar += res;
-		pos += res;
-	}
-	return 0;
-}
-
-static struct ast_channel *phone_new(struct phone_pvt *i, int state, char *cntx, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor)
-{
-	struct ast_format_cap *caps = NULL;
-	struct ast_channel *tmp;
-	struct phone_codec_data queried_codec;
-	struct ast_format *tmpfmt;
-	caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
-	tmp = ast_channel_alloc(1, state, i->cid_num, i->cid_name, "", i->ext, i->context, assignedids, requestor, 0, "Phone/%s", i->dev + 5);
-	if (tmp && caps) {
-		ast_channel_lock(tmp);
-		ast_channel_tech_set(tmp, cur_tech);
-		ast_channel_set_fd(tmp, 0, i->fd);
-		/* XXX Switching formats silently causes kernel panics XXX */
-		if (i->mode == MODE_FXS &&
-		    ioctl(i->fd, PHONE_QUERY_CODEC, &queried_codec) == 0) {
-			if (queried_codec.type == LINEAR16) {
-				ast_format_cap_append(caps, ast_format_slin, 0);
-			} else {
-				ast_format_cap_remove(prefcap, ast_format_slin);
-				ast_format_cap_append_from_cap(caps, prefcap, AST_MEDIA_TYPE_UNKNOWN);
-			}
-		} else {
-			ast_format_cap_append_from_cap(caps, prefcap, AST_MEDIA_TYPE_UNKNOWN);
-		}
-		tmpfmt = ast_format_cap_get_format(caps, 0);
-		ast_channel_nativeformats_set(tmp, caps);
-		ao2_ref(caps, -1);
-		ast_channel_set_rawreadformat(tmp, tmpfmt);
-		ast_channel_set_rawwriteformat(tmp, tmpfmt);
-		ao2_ref(tmpfmt, -1);
-		/* no need to call ast_setstate: the channel_alloc already did its job */
-		if (state == AST_STATE_RING)
-			ast_channel_rings_set(tmp, 1);
-		ast_channel_tech_pvt_set(tmp, i);
-		ast_channel_context_set(tmp, cntx);
-		if (!ast_strlen_zero(i->ext))
-			ast_channel_exten_set(tmp, i->ext);
-		else
-			ast_channel_exten_set(tmp, "s");
-		if (!ast_strlen_zero(i->language))
-			ast_channel_language_set(tmp, i->language);
-
-		/* Don't use ast_set_callerid() here because it will
-		 * generate a NewCallerID event before the NewChannel event */
-		if (!ast_strlen_zero(i->cid_num)) {
-			ast_channel_caller(tmp)->ani.number.valid = 1;
-			ast_channel_caller(tmp)->ani.number.str = ast_strdup(i->cid_num);
-		}
-
-		i->owner = tmp;
-		ast_module_ref(ast_module_info->self);
-		ast_channel_unlock(tmp);
-		if (state != AST_STATE_DOWN) {
-			if (state == AST_STATE_RING) {
-				ioctl(ast_channel_fd(tmp, 0), PHONE_RINGBACK);
-				i->cpt = 1;
-			}
-			if (ast_pbx_start(tmp)) {
-				ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(tmp));
-				ast_hangup(tmp);
-			}
-		}
-	} else {
-		ao2_cleanup(caps);
-		ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
-	}
-	return tmp;
-}
-
-static void phone_mini_packet(struct phone_pvt *i)
-{
-	int res;
-	char buf[1024];
-	/* Ignore stuff we read... */
-	res = read(i->fd, buf, sizeof(buf));
-	if (res < 1) {
-		ast_log(LOG_WARNING, "Read returned %d: %s\n", res, strerror(errno));
-		return;
-	}
-}
-
-static void phone_check_exception(struct phone_pvt *i)
-{
-	int offhook=0;
-	char digit[2] = {0 , 0};
-	union telephony_exception phonee;
-	/* XXX Do something XXX */
-#if 0
-	ast_debug(1, "Exception!\n");
-#endif
-	phonee.bytes = ioctl(i->fd, PHONE_EXCEPTION);
-	if (phonee.bits.dtmf_ready)  {
-		digit[0] = ioctl(i->fd, PHONE_GET_DTMF_ASCII);
-		if (i->mode == MODE_DIALTONE || i->mode == MODE_FXS || i->mode == MODE_SIGMA) {
-			ioctl(i->fd, PHONE_PLAY_STOP);
-			ioctl(i->fd, PHONE_REC_STOP);
-			ioctl(i->fd, PHONE_CPT_STOP);
-			i->dialtone = 0;
-			if (strlen(i->ext) < AST_MAX_EXTENSION - 1)
-				strncat(i->ext, digit, sizeof(i->ext) - strlen(i->ext) - 1);
-			if ((i->mode != MODE_FXS ||
-			     !(phonee.bytes = ioctl(i->fd, PHONE_EXCEPTION)) ||
-			     !phonee.bits.dtmf_ready) &&
-			    ast_exists_extension(NULL, i->context, i->ext, 1, i->cid_num)) {
-				/* It's a valid extension in its context, get moving! */
-				phone_new(i, AST_STATE_RING, i->context, NULL, NULL);
-				/* No need to restart monitor, we are the monitor */
-			} else if (!ast_canmatch_extension(NULL, i->context, i->ext, 1, i->cid_num)) {
-				/* There is nothing in the specified extension that can match anymore.
-				   Try the default */
-				if (ast_exists_extension(NULL, "default", i->ext, 1, i->cid_num)) {
-					/* Check the default, too... */
-					phone_new(i, AST_STATE_RING, "default", NULL, NULL);
-					/* XXX This should probably be justified better XXX */
-				}  else if (!ast_canmatch_extension(NULL, "default", i->ext, 1, i->cid_num)) {
-					/* It's not a valid extension, give a busy signal */
-					ast_debug(1, "%s can't match anything in %s or default\n", i->ext, i->context);
-					ioctl(i->fd, PHONE_BUSY);
-					i->cpt = 1;
-				}
-			}
-#if 0
-			ast_verbose("Extension is %s\n", i->ext);
-#endif
-		}
-	}
-	if (phonee.bits.hookstate) {
-		offhook = ioctl(i->fd, PHONE_HOOKSTATE);
-		if (offhook) {
-			if (i->mode == MODE_IMMEDIATE) {
-				phone_new(i, AST_STATE_RING, i->context, NULL, NULL);
-			} else if (i->mode == MODE_DIALTONE) {
-				ast_module_ref(ast_module_info->self);
-				/* Reset the extension */
-				i->ext[0] = '\0';
-				/* Play the dialtone */
-				i->dialtone++;
-				ioctl(i->fd, PHONE_PLAY_STOP);
-				ioctl(i->fd, PHONE_PLAY_CODEC, ULAW);
-				ioctl(i->fd, PHONE_PLAY_START);
-				ao2_cleanup(i->lastformat);
-				i->lastformat = NULL;
-			} else if (i->mode == MODE_SIGMA) {
-				ast_module_ref(ast_module_info->self);
-				/* Reset the extension */
-				i->ext[0] = '\0';
-				/* Play the dialtone */
-				i->dialtone++;
-				ioctl(i->fd, PHONE_DIALTONE);
-			}
-		} else {
-			if (i->dialtone)
-				ast_module_unref(ast_module_info->self);
-			memset(i->ext, 0, sizeof(i->ext));
-			if (i->cpt)
-			{
-				ioctl(i->fd, PHONE_CPT_STOP);
-				i->cpt = 0;
-			}
-			ioctl(i->fd, PHONE_PLAY_STOP);
-			ioctl(i->fd, PHONE_REC_STOP);
-			i->dialtone = 0;
-			ao2_cleanup(i->lastformat);
-			i->lastformat = NULL;
-		}
-	}
-	if (phonee.bits.pstn_ring) {
-		ast_verbose("Unit is ringing\n");
-		phone_new(i, AST_STATE_RING, i->context, NULL, NULL);
-	}
-	if (phonee.bits.caller_id)
-		ast_verbose("We have caller ID\n");
-
-
-}
-
-static void *do_monitor(void *data)
-{
-	struct pollfd *fds = NULL;
-	int nfds = 0, inuse_fds = 0, res;
-	struct phone_pvt *i;
-	int tonepos = 0;
-	/* The tone we're playing this round */
-	struct timeval to = { 0, 0 };
-	int dotone;
-	/* This thread monitors all the frame relay interfaces which are not yet in use
-	   (and thus do not have a separate thread) indefinitely */
-	while (monitor) {
-		/* Don't let anybody kill us right away.  Nobody should lock the interface list
-		   and wait for the monitor list, but the other way around is okay. */
-		/* Lock the interface list */
-		if (ast_mutex_lock(&iflock)) {
-			ast_log(LOG_ERROR, "Unable to grab interface lock\n");
-			return NULL;
-		}
-		/* Build the stuff we're going to select on, that is the socket of every
-		   phone_pvt that does not have an associated owner channel */
-		i = iflist;
-		dotone = 0;
-		inuse_fds = 0;
-		for (i = iflist; i; i = i->next) {
-			if (!i->owner) {
-				/* This needs to be watched, as it lacks an owner */
-				if (inuse_fds == nfds) {
-					void *tmp = ast_realloc(fds, (nfds + 1) * sizeof(*fds));
-					if (!tmp) {
-						/* Avoid leaking */
-						continue;
-					}
-					fds = tmp;
-					nfds++;
-				}
-				fds[inuse_fds].fd = i->fd;
-				fds[inuse_fds].events = POLLIN | POLLERR;
-				fds[inuse_fds].revents = 0;
-				inuse_fds++;
-
-				if (i->dialtone && i->mode != MODE_SIGMA) {
-					/* Remember we're going to have to come back and play
-					   more dialtones */
-					if (ast_tvzero(to)) {
-						/* If we're due for a dialtone, play one */
-						if (write(i->fd, DialTone + tonepos, 240) != 240) {
-							ast_log(LOG_WARNING, "Dial tone write error\n");
-						}
-					}
-					dotone++;
-				}
-			}
-		}
-		/* Okay, now that we know what to do, release the interface lock */
-		ast_mutex_unlock(&iflock);
-
-		/* Wait indefinitely for something to happen */
-		if (dotone && i && i->mode != MODE_SIGMA) {
-			/* If we're ready to recycle the time, set it to 30 ms */
-			tonepos += 240;
-			if (tonepos >= sizeof(DialTone)) {
-				tonepos = 0;
-			}
-			if (ast_tvzero(to)) {
-				to = ast_tv(0, 30000);
-			}
-			res = ast_poll2(fds, inuse_fds, &to);
-		} else {
-			res = ast_poll(fds, inuse_fds, -1);
-			to = ast_tv(0, 0);
-			tonepos = 0;
-		}
-		/* Okay, select has finished.  Let's see what happened.  */
-		if (res < 0) {
-			ast_debug(1, "poll returned %d: %s\n", res, strerror(errno));
-			continue;
-		}
-		/* If there are no fd's changed, just continue, it's probably time
-		   to play some more dialtones */
-		if (!res) {
-			continue;
-		}
-		/* Alright, lock the interface list again, and let's look and see what has
-		   happened */
-		if (ast_mutex_lock(&iflock)) {
-			ast_log(LOG_WARNING, "Unable to lock the interface list\n");
-			continue;
-		}
-
-		for (i = iflist; i; i = i->next) {
-			int j;
-			/* Find the record */
-			for (j = 0; j < inuse_fds; j++) {
-				if (fds[j].fd == i->fd) {
-					break;
-				}
-			}
-
-			/* Not found? */
-			if (j == inuse_fds) {
-				continue;
-			}
-
-			if (fds[j].revents & POLLIN) {
-				if (i->owner) {
-					continue;
-				}
-				phone_mini_packet(i);
-			}
-			if (fds[j].revents & POLLERR) {
-				if (i->owner) {
-					continue;
-				}
-				phone_check_exception(i);
-			}
-		}
-		ast_mutex_unlock(&iflock);
-	}
-	return NULL;
-}
-
-static int restart_monitor()
-{
-	/* If we're supposed to be stopped -- stay stopped */
-	if (monitor_thread == AST_PTHREADT_STOP)
-		return 0;
-	if (ast_mutex_lock(&monlock)) {
-		ast_log(LOG_WARNING, "Unable to lock monitor\n");
-		return -1;
-	}
-	if (monitor_thread == pthread_self()) {
-		ast_mutex_unlock(&monlock);
-		ast_log(LOG_WARNING, "Cannot kill myself\n");
-		return -1;
-	}
-	if (monitor_thread != AST_PTHREADT_NULL) {
-		if (ast_mutex_lock(&iflock)) {
-			ast_mutex_unlock(&monlock);
-			ast_log(LOG_WARNING, "Unable to lock the interface list\n");
-			return -1;
-		}
-		monitor = 0;
-		while (pthread_kill(monitor_thread, SIGURG) == 0)
-			sched_yield();
-		pthread_join(monitor_thread, NULL);
-		ast_mutex_unlock(&iflock);
-	}
-	monitor = 1;
-	/* Start a new monitor */
-	if (ast_pthread_create_background(&monitor_thread, NULL, do_monitor, NULL) < 0) {
-		ast_mutex_unlock(&monlock);
-		ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
-		return -1;
-	}
-	ast_mutex_unlock(&monlock);
-	return 0;
-}
-
-static struct phone_pvt *mkif(const char *iface, int mode, int txgain, int rxgain)
-{
-	/* Make a phone_pvt structure for this interface */
-	struct phone_pvt *tmp;
-
-	tmp = ast_calloc(1, sizeof(*tmp));
-	if (tmp) {
-		tmp->fd = open(iface, O_RDWR);
-		if (tmp->fd < 0) {
-			ast_log(LOG_WARNING, "Unable to open '%s'\n", iface);
-			ast_free(tmp);
-			return NULL;
-		}
-		if (mode == MODE_FXO) {
-			if (ioctl(tmp->fd, IXJCTL_PORT, PORT_PSTN)) {
-				ast_debug(1, "Unable to set port to PSTN\n");
-			}
-		} else {
-			if (ioctl(tmp->fd, IXJCTL_PORT, PORT_POTS))
-				 if (mode != MODE_FXS)
-				      ast_debug(1, "Unable to set port to POTS\n");
-		}
-		ioctl(tmp->fd, PHONE_PLAY_STOP);
-		ioctl(tmp->fd, PHONE_REC_STOP);
-		ioctl(tmp->fd, PHONE_RING_STOP);
-		ioctl(tmp->fd, PHONE_CPT_STOP);
-		if (ioctl(tmp->fd, PHONE_PSTN_SET_STATE, PSTN_ON_HOOK))
-			ast_debug(1, "ioctl(PHONE_PSTN_SET_STATE) failed on %s (%s)\n",iface, strerror(errno));
-		if (echocancel != AEC_OFF)
-			ioctl(tmp->fd, IXJCTL_AEC_START, echocancel);
-		if (silencesupression)
-			tmp->silencesupression = 1;
-#ifdef PHONE_VAD
-		ioctl(tmp->fd, PHONE_VAD, tmp->silencesupression);
-#endif
-		tmp->mode = mode;
-		ast_fd_set_flags(tmp->fd, O_NONBLOCK);
-		tmp->owner = NULL;
-		ao2_cleanup(tmp->lastformat);
-		tmp->lastformat = NULL;
-		ao2_cleanup(tmp->lastinput);
-		tmp->lastinput = NULL;
-		tmp->ministate = 0;
-		memset(tmp->ext, 0, sizeof(tmp->ext));
-		ast_copy_string(tmp->language, language, sizeof(tmp->language));
-		ast_copy_string(tmp->dev, iface, sizeof(tmp->dev));
-		ast_copy_string(tmp->context, context, sizeof(tmp->context));
-		tmp->next = NULL;
-		tmp->obuflen = 0;
-		tmp->dialtone = 0;
-		tmp->cpt = 0;
-		ast_copy_string(tmp->cid_num, cid_num, sizeof(tmp->cid_num));
-		ast_copy_string(tmp->cid_name, cid_name, sizeof(tmp->cid_name));
-		tmp->txgain = txgain;
-		ioctl(tmp->fd, PHONE_PLAY_VOLUME, tmp->txgain);
-		tmp->rxgain = rxgain;
-		ioctl(tmp->fd, PHONE_REC_VOLUME, tmp->rxgain);
-	}
-	return tmp;
-}
-
-static struct ast_channel *phone_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
-{
-	struct phone_pvt *p;
-	struct ast_channel *tmp = NULL;
-	const char *name = data;
-
-	/* Search for an unowned channel */
-	if (ast_mutex_lock(&iflock)) {
-		ast_log(LOG_ERROR, "Unable to lock interface list???\n");
-		return NULL;
-	}
-	p = iflist;
-	while(p) {
-		if (p->mode == MODE_FXS || (ast_format_cap_iscompatible(cap, phone_tech.capabilities))) {
-			size_t length = strlen(p->dev + 5);
-    		if (strncmp(name, p->dev + 5, length) == 0 &&
-    		    !isalnum(name[length])) {
-    		    if (!p->owner) {
-                     tmp = phone_new(p, AST_STATE_DOWN, p->context, assignedids, requestor);
-                     break;
-                } else
-                     *cause = AST_CAUSE_BUSY;
-            }
-		}
-		p = p->next;
-	}
-	ast_mutex_unlock(&iflock);
-	restart_monitor();
-	if (tmp == NULL) {
-		if (!(ast_format_cap_iscompatible(cap, phone_tech.capabilities))) {
-			struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
-			ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%s'\n",
-				ast_format_cap_get_names(cap, &codec_buf));
-			return NULL;
-		}
-	}
-	return tmp;
-}
-
-/* parse gain value from config file */
-static int parse_gain_value(const char *gain_type, const char *value)
-{
-	float gain;
-
-	/* try to scan number */
-	if (sscanf(value, "%30f", &gain) != 1)
-	{
-		ast_log(LOG_ERROR, "Invalid %s value '%s' in '%s' config\n",
-			value, gain_type, config);
-		return DEFAULT_GAIN;
-	}
-
-	/* multiplicate gain by 1.0 gain value */
-	gain = gain * (float)DEFAULT_GAIN;
-
-	/* percentage? */
-	if (value[strlen(value) - 1] == '%')
-		return (int)(gain / (float)100);
-
-	return (int)gain;
-}
-
-static int __unload_module(void)
-{
-	struct phone_pvt *p, *pl;
-	/* First, take us out of the channel loop */
-	if (cur_tech)
-		ast_channel_unregister(cur_tech);
-	if (!ast_mutex_lock(&iflock)) {
-		/* Hangup all interfaces if they have an owner */
-		p = iflist;
-		while(p) {
-			if (p->owner)
-				ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
-			p = p->next;
-		}
-		iflist = NULL;
-		ast_mutex_unlock(&iflock);
-	} else {
-		ast_log(LOG_WARNING, "Unable to lock the monitor\n");
-		return -1;
-	}
-	if (!ast_mutex_lock(&monlock)) {
-		if (monitor_thread > AST_PTHREADT_NULL) {
-			monitor = 0;
-			while (pthread_kill(monitor_thread, SIGURG) == 0)
-				sched_yield();
-			pthread_join(monitor_thread, NULL);
-		}
-		monitor_thread = AST_PTHREADT_STOP;
-		ast_mutex_unlock(&monlock);
-	} else {
-		ast_log(LOG_WARNING, "Unable to lock the monitor\n");
-		return -1;
-	}
-
-	if (!ast_mutex_lock(&iflock)) {
-		/* Destroy all the interfaces and free their memory */
-		p = iflist;
-		while(p) {
-			/* Close the socket, assuming it's real */
-			if (p->fd > -1)
-				close(p->fd);
-			pl = p;
-			p = p->next;
-			/* Free associated memory */
-			ast_free(pl);
-		}
-		iflist = NULL;
-		ast_mutex_unlock(&iflock);
-	} else {
-		ast_log(LOG_WARNING, "Unable to lock the monitor\n");
-		return -1;
-	}
-
-	ao2_ref(phone_tech.capabilities, -1);
-	ao2_ref(phone_tech_fxs.capabilities, -1);
-	ao2_ref(prefcap, -1);
-
-	return 0;
-}
-
-static int unload_module(void)
-{
-	return __unload_module();
-}
-
-static int load_module(void)
-{
-	struct ast_config *cfg;
-	struct ast_variable *v;
-	struct phone_pvt *tmp;
-	int mode = MODE_IMMEDIATE;
-	int txgain = DEFAULT_GAIN, rxgain = DEFAULT_GAIN; /* default gain 1.0 */
-	struct ast_flags config_flags = { 0 };
-
-	if (!(phone_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	ast_format_cap_append(phone_tech.capabilities, ast_format_g723, 0);
-	ast_format_cap_append(phone_tech.capabilities, ast_format_slin, 0);
-	ast_format_cap_append(phone_tech.capabilities, ast_format_ulaw, 0);
-	ast_format_cap_append(phone_tech.capabilities, ast_format_g729, 0);
-
-	if (!(prefcap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	ast_format_cap_append_from_cap(prefcap, phone_tech.capabilities, AST_MEDIA_TYPE_UNKNOWN);
-	if (!(phone_tech_fxs.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	if ((cfg = ast_config_load(config, config_flags)) == CONFIG_STATUS_FILEINVALID) {
-		ast_log(LOG_ERROR, "Config file %s is in an invalid format.  Aborting.\n", config);
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	/* We *must* have a config file otherwise stop immediately */
-	if (!cfg) {
-		ast_log(LOG_ERROR, "Unable to load config %s\n", config);
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	if (ast_mutex_lock(&iflock)) {
-		/* It's a little silly to lock it, but we mind as well just to be sure */
-		ast_log(LOG_ERROR, "Unable to lock interface list???\n");
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	v = ast_variable_browse(cfg, "interfaces");
-	while(v) {
-		/* Create the interface list */
-		if (!strcasecmp(v->name, "device")) {
-				tmp = mkif(v->value, mode, txgain, rxgain);
-				if (tmp) {
-					tmp->next = iflist;
-					iflist = tmp;
-
-				} else {
-					ast_log(LOG_ERROR, "Unable to register channel '%s'\n", v->value);
-					ast_config_destroy(cfg);
-					ast_mutex_unlock(&iflock);
-					__unload_module();
-					return AST_MODULE_LOAD_DECLINE;
-				}
-		} else if (!strcasecmp(v->name, "silencesupression")) {
-			silencesupression = ast_true(v->value);
-		} else if (!strcasecmp(v->name, "language")) {
-			ast_copy_string(language, v->value, sizeof(language));
-		} else if (!strcasecmp(v->name, "callerid")) {
-			ast_callerid_split(v->value, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num));
-		} else if (!strcasecmp(v->name, "mode")) {
-			if (!strncasecmp(v->value, "di", 2))
-				mode = MODE_DIALTONE;
-			else if (!strncasecmp(v->value, "sig", 3))
-				mode = MODE_SIGMA;
-			else if (!strncasecmp(v->value, "im", 2))
-				mode = MODE_IMMEDIATE;
-			else if (!strncasecmp(v->value, "fxs", 3)) {
-				mode = MODE_FXS;
-				ast_format_cap_remove_by_type(prefcap, AST_MEDIA_TYPE_AUDIO); /* All non-voice */
-			}
-			else if (!strncasecmp(v->value, "fx", 2))
-				mode = MODE_FXO;
-			else
-				ast_log(LOG_WARNING, "Unknown mode: %s\n", v->value);
-		} else if (!strcasecmp(v->name, "context")) {
-			ast_copy_string(context, v->value, sizeof(context));
-		} else if (!strcasecmp(v->name, "format")) {
-			if (!strcasecmp(v->value, "g729")) {
-				ast_format_cap_remove_by_type(prefcap, AST_MEDIA_TYPE_UNKNOWN);
-				ast_format_cap_append(prefcap, ast_format_g729, 0);
-			} else if (!strcasecmp(v->value, "g723.1")) {
-				ast_format_cap_remove_by_type(prefcap, AST_MEDIA_TYPE_UNKNOWN);
-				ast_format_cap_append(prefcap, ast_format_g723, 0);
-			} else if (!strcasecmp(v->value, "slinear")) {
-				if (mode == MODE_FXS) {
-					ast_format_cap_append(prefcap, ast_format_slin, 0);
-				} else {
-					ast_format_cap_remove_by_type(prefcap, AST_MEDIA_TYPE_UNKNOWN);
-					ast_format_cap_append(prefcap, ast_format_slin, 0);
-				}
-			} else if (!strcasecmp(v->value, "ulaw")) {
-				ast_format_cap_remove_by_type(prefcap, AST_MEDIA_TYPE_UNKNOWN);
-				ast_format_cap_append(prefcap, ast_format_ulaw, 0);
-			} else
-				ast_log(LOG_WARNING, "Unknown format '%s'\n", v->value);
-		} else if (!strcasecmp(v->name, "echocancel")) {
-			if (!strcasecmp(v->value, "off")) {
-				echocancel = AEC_OFF;
-			} else if (!strcasecmp(v->value, "low")) {
-				echocancel = AEC_LOW;
-			} else if (!strcasecmp(v->value, "medium")) {
-				echocancel = AEC_MED;
-			} else if (!strcasecmp(v->value, "high")) {
-				echocancel = AEC_HIGH;
-			} else
-				ast_log(LOG_WARNING, "Unknown echo cancellation '%s'\n", v->value);
-		} else if (!strcasecmp(v->name, "txgain")) {
-			txgain = parse_gain_value(v->name, v->value);
-		} else if (!strcasecmp(v->name, "rxgain")) {
-			rxgain = parse_gain_value(v->name, v->value);
-		}
-		v = v->next;
-	}
-	ast_mutex_unlock(&iflock);
-
-	if (mode == MODE_FXS) {
-		ast_format_cap_append_from_cap(phone_tech_fxs.capabilities, prefcap, AST_MEDIA_TYPE_UNKNOWN);
-		cur_tech = &phone_tech_fxs;
-	} else
-		cur_tech = (struct ast_channel_tech *) &phone_tech;
-
-	/* Make sure we can register our Adtranphone channel type */
-
-	if (ast_channel_register(cur_tech)) {
-		ast_log(LOG_ERROR, "Unable to register channel class 'Phone'\n");
-		ast_config_destroy(cfg);
-		__unload_module();
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	ast_config_destroy(cfg);
-	/* And start the monitor for the first time */
-	restart_monitor();
-	return AST_MODULE_LOAD_SUCCESS;
-}
-
-AST_MODULE_INFO_STANDARD_DEPRECATED(ASTERISK_GPL_KEY, "Linux Telephony API Support");
diff --git a/channels/chan_phone.h b/channels/chan_phone.h
deleted file mode 100644
index d15570850ff601b70e5312f8649b2330d9880b3f..0000000000000000000000000000000000000000
--- a/channels/chan_phone.h
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
-  * 8-bit raw data
-  *
-  * Source: DialTone.ulaw
-  *
-  * Copyright (C) 1999, Mark Spencer
-  *
-  * Distributed under the terms of the GNU General Public License
-  *
-  */
-
-/*! \file
- * \brief
- * 8-bit raw data
- */
-
-static unsigned char DialTone[] = {
-0xff, 0xab, 0x9d, 0x96, 0x91, 0x90, 0x91, 0x96, 0x9c, 0xaa,
-0xd9, 0x2f, 0x1f, 0x19, 0x15, 0x14, 0x15, 0x19, 0x1f, 0x2c,
-0x4e, 0xb9, 0xa7, 0x9f, 0x9c, 0x9b, 0x9c, 0x9f, 0xa7, 0xb3,
-0xcf, 0x47, 0x34, 0x2d, 0x2a, 0x2a, 0x2c, 0x31, 0x3a, 0x47,
-0x5f, 0xe4, 0xd8, 0xd9, 0xe9, 0x64, 0x4f, 0x49, 0x46, 0x49,
-0x58, 0xde, 0xc2, 0xb5, 0xad, 0xa8, 0xa6, 0xa6, 0xa9, 0xaf,
-0xbf, 0x56, 0x32, 0x26, 0x1e, 0x1b, 0x19, 0x1a, 0x1d, 0x24,
-0x33, 0xdd, 0xad, 0x9f, 0x98, 0x94, 0x92, 0x93, 0x97, 0x9e,
-0xac, 0xf8, 0x2c, 0x1d, 0x16, 0x11, 0xf, 0x10, 0x15, 0x1b,
-0x29, 0x55, 0xae, 0x9e, 0x97, 0x92, 0x90, 0x91, 0x95, 0x9c,
-0xa8, 0xca, 0x35, 0x22, 0x1a, 0x16, 0x15, 0x16, 0x19, 0x1f,
-0x2b, 0x47, 0xbe, 0xab, 0xa2, 0x9e, 0x9d, 0x9e, 0xa2, 0xa9,
-0xb4, 0xcc, 0x4f, 0x3a, 0x32, 0x2f, 0x2f, 0x32, 0x39, 0x41,
-0x4f, 0x67, 0xf5, 0xf5, 0x67, 0x51, 0x46, 0x3e, 0x3b, 0x3b,
-0x3f, 0x4d, 0xe2, 0xbe, 0xb0, 0xa9, 0xa4, 0xa1, 0xa1, 0xa5,
-0xab, 0xba, 0x64, 0x33, 0x25, 0x1d, 0x19, 0x17, 0x18, 0x1b,
-0x20, 0x2e, 0x72, 0xae, 0x9f, 0x98, 0x94, 0x91, 0x92, 0x96,
-0x9c, 0xa9, 0xd4, 0x2f, 0x1e, 0x17, 0x11, 0xf, 0x10, 0x14,
-0x1a, 0x26, 0x48, 0xb2, 0x9f, 0x98, 0x93, 0x91, 0x92, 0x95,
-0x9b, 0xa7, 0xc1, 0x3a, 0x25, 0x1c, 0x18, 0x16, 0x17, 0x1a,
-0x1f, 0x2b, 0x42, 0xc6, 0xae, 0xa6, 0xa0, 0x9f, 0xa0, 0xa5,
-0xab, 0xb6, 0xcb, 0x5a, 0x40, 0x39, 0x36, 0x37, 0x3b, 0x43,
-0x4e, 0x60, 0x7b, 0x7c, 0x60, 0x4e, 0x41, 0x3a, 0x34, 0x32,
-0x33, 0x39, 0x45, 0xed, 0xbd, 0xae, 0xa6, 0xa0, 0x9e, 0x9e,
-0xa0, 0xa8, 0xb4, 0xef, 0x34, 0x24, 0x1c, 0x18, 0x16, 0x16,
-0x19, 0x1e, 0x2b, 0x54, 0xb1, 0x9f, 0x98, 0x93, 0x91, 0x91,
-0x95, 0x9b, 0xa6, 0xc6, 0x33, 0x1f, 0x17, 0x12, 0xf, 0x10,
-0x13, 0x1a, 0x24, 0x3e, 0xb8, 0xa2, 0x99, 0x94, 0x92, 0x92,
-0x96, 0x9b, 0xa6, 0xbc, 0x40, 0x29, 0x1e, 0x1a, 0x18, 0x19,
-0x1b, 0x20, 0x2b, 0x3f, 0xcf, 0xb3, 0xa9, 0xa5, 0xa3, 0xa4,
-0xa8, 0xae, 0xb9, 0xcc, 0x67, 0x49, 0x40, 0x3f, 0x42, 0x4a,
-0x59, 0x79, 0xe5, 0xe4, 0x7a, 0x54, 0x43, 0x39, 0x31, 0x2d,
-0x2c, 0x2d, 0x32, 0x3e, 0x71, 0xbd, 0xad, 0xa4, 0x9e, 0x9c,
-0x9c, 0x9e, 0xa4, 0xaf, 0xd7, 0x36, 0x24, 0x1c, 0x17, 0x15,
-0x15, 0x17, 0x1d, 0x28, 0x47, 0xb5, 0xa1, 0x98, 0x93, 0x90,
-0x90, 0x93, 0x99, 0xa4, 0xbd, 0x38, 0x21, 0x18, 0x13, 0x10,
-0x10, 0x13, 0x19, 0x22, 0x39, 0xbe, 0xa5, 0x9b, 0x96, 0x93,
-0x93, 0x96, 0x9b, 0xa5, 0xb9, 0x4a, 0x2c, 0x20, 0x1c, 0x1a,
-0x1a, 0x1d, 0x22, 0x2c, 0x3d, 0xdc, 0xb8, 0xad, 0xa9, 0xa8,
-0xa9, 0xac, 0xb2, 0xbd, 0xce, 0x78, 0x54, 0x4d, 0x4f, 0x5a,
-0xff, 0xda, 0xcf, 0xcd, 0xd4, 0xf8, 0x4e, 0x3d, 0x32, 0x2c,
-0x29, 0x28, 0x29, 0x2d, 0x38, 0x5c, 0xbd, 0xac, 0xa2, 0x9d,
-0x9a, 0x9a, 0x9c, 0xa0, 0xac, 0xca, 0x39, 0x25, 0x1b, 0x16,
-0x13, 0x13, 0x16, 0x1b, 0x25, 0x3e, 0xb9, 0xa2, 0x99, 0x93,
-0x90, 0x90, 0x93, 0x98, 0xa1, 0xb8, 0x3d, 0x24, 0x19, 0x13,
-0x10, 0x10, 0x13, 0x18, 0x21, 0x35, 0xc7, 0xa8, 0x9d, 0x97,
-0x95, 0x95, 0x97, 0x9c, 0xa4, 0xb6, 0x57, 0x2f, 0x24, 0x1e,
-0x1c, 0x1c, 0x1e, 0x24, 0x2d, 0x3d, 0xf1, 0xbe, 0xb2, 0xad,
-0xac, 0xad, 0xb1, 0xb9, 0xc3, 0xd4, 0xfa, 0x64, 0x65, 0xf9,
-0xd9, 0xca, 0xc2, 0xbf, 0xc0, 0xc9, 0xe7, 0x4c, 0x39, 0x2e,
-0x28, 0x24, 0x23, 0x25, 0x29, 0x33, 0x4f, 0xbf, 0xab, 0xa0,
-0x9b, 0x99, 0x98, 0x9a, 0x9e, 0xa9, 0xc0, 0x3c, 0x26, 0x1b,
-0x16, 0x12, 0x12, 0x14, 0x19, 0x22, 0x38, 0xbe, 0xa4, 0x9a,
-0x93, 0x90, 0x8f, 0x92, 0x97, 0x9f, 0xb3, 0x46, 0x26, 0x1b,
-0x15, 0x11, 0x11, 0x13, 0x18, 0x1f, 0x31, 0xd4, 0xab, 0x9e,
-0x99, 0x96, 0x96, 0x98, 0x9c, 0xa4, 0xb4, 0x6f, 0x34, 0x28,
-0x20, 0x1e, 0x1e, 0x20, 0x26, 0x2e, 0x3d, 0x6d, 0xc5, 0xb9,
-0xb3, 0xb2, 0xb4, 0xba, 0xc1, 0xcd, 0xe0, 0xfc, 0xfb, 0xe0,
-0xce, 0xc3, 0xbb, 0xb7, 0xb6, 0xb9, 0xc0, 0xda, 0x4b, 0x36,
-0x2b, 0x25, 0x20, 0x1f, 0x20, 0x26, 0x2e, 0x46, 0xc2, 0xab,
-0x9f, 0x9a, 0x97, 0x96, 0x98, 0x9c, 0xa5, 0xba, 0x41, 0x27,
-0x1b, 0x15, 0x12, 0x11, 0x13, 0x18, 0x1f, 0x32, 0xc8, 0xa6,
-0x9a, 0x94, 0x90, 0x8f, 0x91, 0x97, 0x9e, 0xaf, 0x54, 0x29,
-0x1c, 0x16, 0x12, 0x11, 0x14, 0x18, 0x1f, 0x2e, 0xf2, 0xae,
-0xa0, 0x9b, 0x98, 0x97, 0x99, 0x9d, 0xa5, 0xb3, 0xe4, 0x3a,
-0x2b, 0x25, 0x21, 0x21, 0x24, 0x29, 0x30, 0x3e, 0x62, 0xcd,
-0xbf, 0xbb, 0xbb, 0xbe, 0xc6, 0xd1, 0xe7, 0x76, 0x75, 0xe7,
-0xcf, 0xc1, 0xb9, 0xb2, 0xaf, 0xaf, 0xb2, 0xba, 0xcf, 0x4c,
-0x34, 0x29, 0x22, 0x1e, 0x1d, 0x1e, 0x22, 0x2b, 0x3e, 0xc7,
-0xab, 0x9f, 0x99, 0x96, 0x95, 0x96, 0x9a, 0xa2, 0xb5, 0x4a,
-0x28, 0x1c, 0x15, 0x11, 0x10, 0x12, 0x17, 0x1e, 0x2e, 0xd5,
-0xa9, 0x9b, 0x95, 0x90, 0x8f, 0x91, 0x96, 0x9d, 0xac, 0x78,
-0x2c, 0x1e, 0x17, 0x13, 0x12, 0x14, 0x18, 0x1f, 0x2d, 0x5d,
-0xb3, 0xa4, 0x9d, 0x9a, 0x99, 0x9b, 0x9e, 0xa6, 0xb2, 0xd6,
-0x3f, 0x2f, 0x29, 0x26, 0x26, 0x28, 0x2d, 0x35, 0x42, 0x5e,
-0xd8, 0xca, 0xc6, 0xc9, 0xcf, 0xe4, 0x69, 0x59, 0x58, 0x64,
-0xdf, 0xc7, 0xba, 0xb1, 0xac, 0xaa, 0xaa, 0xad, 0xb4, 0xc7,
-0x4f, 0x33, 0x27, 0x1f, 0x1c, 0x1b, 0x1c, 0x1f, 0x27, 0x39,
-0xce, 0xac, 0x9f, 0x99, 0x95, 0x94, 0x95, 0x99, 0x9f, 0xaf,
-0x59, 0x2a, 0x1c, 0x16, 0x11, 0x10, 0x11, 0x16, 0x1d, 0x2b,
-0xff, 0xab, 0x9d, 0x96, 0x91, 0x90, 0x91, 0x96, 0x9c, 0xaa,
-0xd9, 0x2f, 0x1f, 0x19, 0x15, 0x14, 0x15, 0x19, 0x1f, 0x2c,
-0x4e, 0xb9, 0xa7, 0x9f, 0x9c, 0x9b, 0x9c, 0x9f, 0xa7, 0xb3,
-0xcf, 0x47, 0x34, 0x2d, 0x2a, 0x2a, 0x2c, 0x31, 0x3a, 0x47,
-0x5f, 0xe4, 0xd8, 0xd9, 0xe9, 0x64, 0x4f, 0x49, 0x46, 0x49,
-0x58, 0xde, 0xc2, 0xb5, 0xad, 0xa8, 0xa6, 0xa6, 0xa9, 0xaf,
-0xbf, 0x56, 0x32, 0x26, 0x1e, 0x1b, 0x19, 0x1a, 0x1d, 0x24,
-0x33, 0xdd, 0xad, 0x9f, 0x98, 0x94, 0x92, 0x93, 0x97, 0x9e,
-0xac, 0xf8, 0x2c, 0x1d, 0x16, 0x11, 0xf, 0x10, 0x15, 0x1b,
-0x29, 0x55, 0xae, 0x9e, 0x97, 0x92, 0x90, 0x91, 0x95, 0x9c,
-0xa8, 0xca, 0x35, 0x22, 0x1a, 0x16, 0x15, 0x16, 0x19, 0x1f,
-0x2b, 0x47, 0xbe, 0xab, 0xa2, 0x9e, 0x9d, 0x9e, 0xa2, 0xa9,
-0xb4, 0xcc, 0x4f, 0x3a, 0x32, 0x2f, 0x2f, 0x32, 0x39, 0x41,
-0x4f, 0x67, 0xf5, 0xf5, 0x67, 0x51, 0x46, 0x3e, 0x3b, 0x3b,
-0x3f, 0x4d, 0xe2, 0xbe, 0xb0, 0xa9, 0xa4, 0xa1, 0xa1, 0xa5,
-0xab, 0xba, 0x64, 0x33, 0x25, 0x1d, 0x19, 0x17, 0x18, 0x1b,
-0x20, 0x2e, 0x72, 0xae, 0x9f, 0x98, 0x94, 0x91, 0x92, 0x96,
-0x9c, 0xa9, 0xd4, 0x2f, 0x1e, 0x17, 0x11, 0xf, 0x10, 0x14,
-0x1a, 0x26, 0x48, 0xb2, 0x9f, 0x98, 0x93, 0x91, 0x92, 0x95,
-0x9b, 0xa7, 0xc1, 0x3a, 0x25, 0x1c, 0x18, 0x16, 0x17, 0x1a,
-0x1f, 0x2b, 0x42, 0xc6, 0xae, 0xa6, 0xa0, 0x9f, 0xa0, 0xa5,
-0xab, 0xb6, 0xcb, 0x5a, 0x40, 0x39, 0x36, 0x37, 0x3b, 0x43,
-0x4e, 0x60, 0x7b, 0x7c, 0x60, 0x4e, 0x41, 0x3a, 0x34, 0x32,
-0x33, 0x39, 0x45, 0xed, 0xbd, 0xae, 0xa6, 0xa0, 0x9e, 0x9e,
-0xa0, 0xa8, 0xb4, 0xef, 0x34, 0x24, 0x1c, 0x18, 0x16, 0x16,
-0x19, 0x1e, 0x2b, 0x54, 0xb1, 0x9f, 0x98, 0x93, 0x91, 0x91,
-0x95, 0x9b, 0xa6, 0xc6, 0x33, 0x1f, 0x17, 0x12, 0xf, 0x10,
-0x13, 0x1a, 0x24, 0x3e, 0xb8, 0xa2, 0x99, 0x94, 0x92, 0x92,
-0x96, 0x9b, 0xa6, 0xbc, 0x40, 0x29, 0x1e, 0x1a, 0x18, 0x19,
-0x1b, 0x20, 0x2b, 0x3f, 0xcf, 0xb3, 0xa9, 0xa5, 0xa3, 0xa4,
-0xa8, 0xae, 0xb9, 0xcc, 0x67, 0x49, 0x40, 0x3f, 0x42, 0x4a,
-0x59, 0x79, 0xe5, 0xe4, 0x7a, 0x54, 0x43, 0x39, 0x31, 0x2d,
-0x2c, 0x2d, 0x32, 0x3e, 0x71, 0xbd, 0xad, 0xa4, 0x9e, 0x9c,
-0x9c, 0x9e, 0xa4, 0xaf, 0xd7, 0x36, 0x24, 0x1c, 0x17, 0x15,
-0x15, 0x17, 0x1d, 0x28, 0x47, 0xb5, 0xa1, 0x98, 0x93, 0x90,
-0x90, 0x93, 0x99, 0xa4, 0xbd, 0x38, 0x21, 0x18, 0x13, 0x10,
-0x10, 0x13, 0x19, 0x22, 0x39, 0xbe, 0xa5, 0x9b, 0x96, 0x93,
-0x93, 0x96, 0x9b, 0xa5, 0xb9, 0x4a, 0x2c, 0x20, 0x1c, 0x1a,
-0x1a, 0x1d, 0x22, 0x2c, 0x3d, 0xdc, 0xb8, 0xad, 0xa9, 0xa8,
-0xa9, 0xac, 0xb2, 0xbd, 0xce, 0x78, 0x54, 0x4d, 0x4f, 0x5a,
-0xff, 0xda, 0xcf, 0xcd, 0xd4, 0xf8, 0x4e, 0x3d, 0x32, 0x2c,
-0x29, 0x28, 0x29, 0x2d, 0x38, 0x5c, 0xbd, 0xac, 0xa2, 0x9d,
-0x9a, 0x9a, 0x9c, 0xa0, 0xac, 0xca, 0x39, 0x25, 0x1b, 0x16,
-0x13, 0x13, 0x16, 0x1b, 0x25, 0x3e, 0xb9, 0xa2, 0x99, 0x93,
-0x90, 0x90, 0x93, 0x98, 0xa1, 0xb8, 0x3d, 0x24, 0x19, 0x13,
-0x10, 0x10, 0x13, 0x18, 0x21, 0x35, 0xc7, 0xa8, 0x9d, 0x97,
-0x95, 0x95, 0x97, 0x9c, 0xa4, 0xb6, 0x57, 0x2f, 0x24, 0x1e,
-0x1c, 0x1c, 0x1e, 0x24, 0x2d, 0x3d, 0xf1, 0xbe, 0xb2, 0xad,
-0xac, 0xad, 0xb1, 0xb9, 0xc3, 0xd4, 0xfa, 0x64, 0x65, 0xf9,
-0xd9, 0xca, 0xc2, 0xbf, 0xc0, 0xc9, 0xe7, 0x4c, 0x39, 0x2e,
-0x28, 0x24, 0x23, 0x25, 0x29, 0x33, 0x4f, 0xbf, 0xab, 0xa0,
-0x9b, 0x99, 0x98, 0x9a, 0x9e, 0xa9, 0xc0, 0x3c, 0x26, 0x1b,
-0x16, 0x12, 0x12, 0x14, 0x19, 0x22, 0x38, 0xbe, 0xa4, 0x9a,
-0x93, 0x90, 0x8f, 0x92, 0x97, 0x9f, 0xb3, 0x46, 0x26, 0x1b,
-0x15, 0x11, 0x11, 0x13, 0x18, 0x1f, 0x31, 0xd4, 0xab, 0x9e,
-0x99, 0x96, 0x96, 0x98, 0x9c, 0xa4, 0xb4, 0x6f, 0x34, 0x28,
-0x20, 0x1e, 0x1e, 0x20, 0x26, 0x2e, 0x3d, 0x6d, 0xc5, 0xb9,
-0xb3, 0xb2, 0xb4, 0xba, 0xc1, 0xcd, 0xe0, 0xfc, 0xfb, 0xe0,
-0xce, 0xc3, 0xbb, 0xb7, 0xb6, 0xb9, 0xc0, 0xda, 0x4b, 0x36,
-0x2b, 0x25, 0x20, 0x1f, 0x20, 0x26, 0x2e, 0x46, 0xc2, 0xab,
-0x9f, 0x9a, 0x97, 0x96, 0x98, 0x9c, 0xa5, 0xba, 0x41, 0x27,
-0x1b, 0x15, 0x12, 0x11, 0x13, 0x18, 0x1f, 0x32, 0xc8, 0xa6,
-0x9a, 0x94, 0x90, 0x8f, 0x91, 0x97, 0x9e, 0xaf, 0x54, 0x29,
-0x1c, 0x16, 0x12, 0x11, 0x14, 0x18, 0x1f, 0x2e, 0xf2, 0xae,
-0xa0, 0x9b, 0x98, 0x97, 0x99, 0x9d, 0xa5, 0xb3, 0xe4, 0x3a,
-0x2b, 0x25, 0x21, 0x21, 0x24, 0x29, 0x30, 0x3e, 0x62, 0xcd,
-0xbf, 0xbb, 0xbb, 0xbe, 0xc6, 0xd1, 0xe7, 0x76, 0x75, 0xe7,
-0xcf, 0xc1, 0xb9, 0xb2, 0xaf, 0xaf, 0xb2, 0xba, 0xcf, 0x4c,
-0x34, 0x29, 0x22, 0x1e, 0x1d, 0x1e, 0x22, 0x2b, 0x3e, 0xc7,
-0xab, 0x9f, 0x99, 0x96, 0x95, 0x96, 0x9a, 0xa2, 0xb5, 0x4a,
-0x28, 0x1c, 0x15, 0x11, 0x10, 0x12, 0x17, 0x1e, 0x2e, 0xd5,
-0xa9, 0x9b, 0x95, 0x90, 0x8f, 0x91, 0x96, 0x9d, 0xac, 0x78,
-0x2c, 0x1e, 0x17, 0x13, 0x12, 0x14, 0x18, 0x1f, 0x2d, 0x5d,
-0xb3, 0xa4, 0x9d, 0x9a, 0x99, 0x9b, 0x9e, 0xa6, 0xb2, 0xd6,
-0x3f, 0x2f, 0x29, 0x26, 0x26, 0x28, 0x2d, 0x35, 0x42, 0x5e,
-0xd8, 0xca, 0xc6, 0xc9, 0xcf, 0xe4, 0x69, 0x59, 0x58, 0x64,
-0xdf, 0xc7, 0xba, 0xb1, 0xac, 0xaa, 0xaa, 0xad, 0xb4, 0xc7,
-0x4f, 0x33, 0x27, 0x1f, 0x1c, 0x1b, 0x1c, 0x1f, 0x27, 0x39,
-0xce, 0xac, 0x9f, 0x99, 0x95, 0x94, 0x95, 0x99, 0x9f, 0xaf,
-0x59, 0x2a, 0x1c, 0x16, 0x11, 0x10, 0x11, 0x16, 0x1d, 0x2b,
-0xff, 0xab, 0x9d, 0x96, 0x91, 0x90, 0x91, 0x96, 0x9c, 0xaa,
-0xd9, 0x2f, 0x1f, 0x19, 0x15, 0x14, 0x15, 0x19, 0x1f, 0x2c,
-0x4e, 0xb9, 0xa7, 0x9f, 0x9c, 0x9b, 0x9c, 0x9f, 0xa7, 0xb3,
-0xcf, 0x47, 0x34, 0x2d, 0x2a, 0x2a, 0x2c, 0x31, 0x3a, 0x47,
-0x5f, 0xe4, 0xd8, 0xd9, 0xe9, 0x64, 0x4f, 0x49, 0x46, 0x49,
-0x58, 0xde, 0xc2, 0xb5, 0xad, 0xa8, 0xa6, 0xa6, 0xa9, 0xaf,
-0xbf, 0x56, 0x32, 0x26, 0x1e, 0x1b, 0x19, 0x1a, 0x1d, 0x24,
-0x33, 0xdd, 0xad, 0x9f, 0x98, 0x94, 0x92, 0x93, 0x97, 0x9e,
-0xac, 0xf8, 0x2c, 0x1d, 0x16, 0x11, 0xf, 0x10, 0x15, 0x1b,
-0x29, 0x55, 0xae, 0x9e, 0x97, 0x92, 0x90, 0x91, 0x95, 0x9c,
-0xa8, 0xca, 0x35, 0x22, 0x1a, 0x16, 0x15, 0x16, 0x19, 0x1f,
-0x2b, 0x47, 0xbe, 0xab, 0xa2, 0x9e, 0x9d, 0x9e, 0xa2, 0xa9,
-0xb4, 0xcc, 0x4f, 0x3a, 0x32, 0x2f, 0x2f, 0x32, 0x39, 0x41,
-0x4f, 0x67, 0xf5, 0xf5, 0x67, 0x51, 0x46, 0x3e, 0x3b, 0x3b,
-0x3f, 0x4d, 0xe2, 0xbe, 0xb0, 0xa9, 0xa4, 0xa1, 0xa1, 0xa5,
-0xab, 0xba, 0x64, 0x33, 0x25, 0x1d, 0x19, 0x17, 0x18, 0x1b,
-0x20, 0x2e, 0x72, 0xae, 0x9f, 0x98, 0x94, 0x91, 0x92, 0x96,
-0x9c, 0xa9, 0xd4, 0x2f, 0x1e, 0x17, 0x11, 0xf, 0x10, 0x14,
-0x1a, 0x26, 0x48, 0xb2, 0x9f, 0x98, 0x93, 0x91, 0x92, 0x95,
-0x9b, 0xa7, 0xc1, 0x3a, 0x25, 0x1c, 0x18, 0x16, 0x17, 0x1a,
-0x1f, 0x2b, 0x42, 0xc6, 0xae, 0xa6, 0xa0, 0x9f, 0xa0, 0xa5,
-0xab, 0xb6, 0xcb, 0x5a, 0x40, 0x39, 0x36, 0x37, 0x3b, 0x43,
-0x4e, 0x60, 0x7b, 0x7c, 0x60, 0x4e, 0x41, 0x3a, 0x34, 0x32,
-0x33, 0x39, 0x45, 0xed, 0xbd, 0xae, 0xa6, 0xa0, 0x9e, 0x9e,
-0xa0, 0xa8, 0xb4, 0xef, 0x34, 0x24, 0x1c, 0x18, 0x16, 0x16,
-0x19, 0x1e, 0x2b, 0x54, 0xb1, 0x9f, 0x98, 0x93, 0x91, 0x91,
-0x95, 0x9b, 0xa6, 0xc6, 0x33, 0x1f, 0x17, 0x12, 0xf, 0x10,
-0x13, 0x1a, 0x24, 0x3e, 0xb8, 0xa2, 0x99, 0x94, 0x92, 0x92,
-0x96, 0x9b, 0xa6, 0xbc, 0x40, 0x29, 0x1e, 0x1a, 0x18, 0x19,
-0x1b, 0x20, 0x2b, 0x3f, 0xcf, 0xb3, 0xa9, 0xa5, 0xa3, 0xa4,
-0xa8, 0xae, 0xb9, 0xcc, 0x67, 0x49, 0x40, 0x3f, 0x42, 0x4a,
-0x59, 0x79, 0xe5, 0xe4, 0x7a, 0x54, 0x43, 0x39, 0x31, 0x2d,
-0x2c, 0x2d, 0x32, 0x3e, 0x71, 0xbd, 0xad, 0xa4, 0x9e, 0x9c,
-0x9c, 0x9e, 0xa4, 0xaf, 0xd7, 0x36, 0x24, 0x1c, 0x17, 0x15,
-0x15, 0x17, 0x1d, 0x28, 0x47, 0xb5, 0xa1, 0x98, 0x93, 0x90,
-0x90, 0x93, 0x99, 0xa4, 0xbd, 0x38, 0x21, 0x18, 0x13, 0x10,
-0x10, 0x13, 0x19, 0x22, 0x39, 0xbe, 0xa5, 0x9b, 0x96, 0x93,
-0x93, 0x96, 0x9b, 0xa5, 0xb9, 0x4a, 0x2c, 0x20, 0x1c, 0x1a,
-0x1a, 0x1d, 0x22, 0x2c, 0x3d, 0xdc, 0xb8, 0xad, 0xa9, 0xa8,
-0xa9, 0xac, 0xb2, 0xbd, 0xce, 0x78, 0x54, 0x4d, 0x4f, 0x5a,
-0xff, 0xda, 0xcf, 0xcd, 0xd4, 0xf8, 0x4e, 0x3d, 0x32, 0x2c,
-0x29, 0x28, 0x29, 0x2d, 0x38, 0x5c, 0xbd, 0xac, 0xa2, 0x9d,
-0x9a, 0x9a, 0x9c, 0xa0, 0xac, 0xca, 0x39, 0x25, 0x1b, 0x16,
-0x13, 0x13, 0x16, 0x1b, 0x25, 0x3e, 0xb9, 0xa2, 0x99, 0x93,
-0x90, 0x90, 0x93, 0x98, 0xa1, 0xb8, 0x3d, 0x24, 0x19, 0x13,
-0x10, 0x10, 0x13, 0x18, 0x21, 0x35, 0xc7, 0xa8, 0x9d, 0x97,
-0x95, 0x95, 0x97, 0x9c, 0xa4, 0xb6, 0x57, 0x2f, 0x24, 0x1e,
-0x1c, 0x1c, 0x1e, 0x24, 0x2d, 0x3d, 0xf1, 0xbe, 0xb2, 0xad,
-0xac, 0xad, 0xb1, 0xb9, 0xc3, 0xd4, 0xfa, 0x64, 0x65, 0xf9,
-0xd9, 0xca, 0xc2, 0xbf, 0xc0, 0xc9, 0xe7, 0x4c, 0x39, 0x2e,
-0x28, 0x24, 0x23, 0x25, 0x29, 0x33, 0x4f, 0xbf, 0xab, 0xa0,
-0x9b, 0x99, 0x98, 0x9a, 0x9e, 0xa9, 0xc0, 0x3c, 0x26, 0x1b,
-0x16, 0x12, 0x12, 0x14, 0x19, 0x22, 0x38, 0xbe, 0xa4, 0x9a,
-0x93, 0x90, 0x8f, 0x92, 0x97, 0x9f, 0xb3, 0x46, 0x26, 0x1b,
-0x15, 0x11, 0x11, 0x13, 0x18, 0x1f, 0x31, 0xd4, 0xab, 0x9e,
-0x99, 0x96, 0x96, 0x98, 0x9c, 0xa4, 0xb4, 0x6f, 0x34, 0x28,
-0x20, 0x1e, 0x1e, 0x20, 0x26, 0x2e, 0x3d, 0x6d, 0xc5, 0xb9,
-0xb3, 0xb2, 0xb4, 0xba, 0xc1, 0xcd, 0xe0, 0xfc, 0xfb, 0xe0,
-0xce, 0xc3, 0xbb, 0xb7, 0xb6, 0xb9, 0xc0, 0xda, 0x4b, 0x36,
-0x2b, 0x25, 0x20, 0x1f, 0x20, 0x26, 0x2e, 0x46, 0xc2, 0xab,
-0x9f, 0x9a, 0x97, 0x96, 0x98, 0x9c, 0xa5, 0xba, 0x41, 0x27,
-0x1b, 0x15, 0x12, 0x11, 0x13, 0x18, 0x1f, 0x32, 0xc8, 0xa6,
-0x9a, 0x94, 0x90, 0x8f, 0x91, 0x97, 0x9e, 0xaf, 0x54, 0x29,
-0x1c, 0x16, 0x12, 0x11, 0x14, 0x18, 0x1f, 0x2e, 0xf2, 0xae,
-0xa0, 0x9b, 0x98, 0x97, 0x99, 0x9d, 0xa5, 0xb3, 0xe4, 0x3a,
-0x2b, 0x25, 0x21, 0x21, 0x24, 0x29, 0x30, 0x3e, 0x62, 0xcd,
-0xbf, 0xbb, 0xbb, 0xbe, 0xc6, 0xd1, 0xe7, 0x76, 0x75, 0xe7,
-0xcf, 0xc1, 0xb9, 0xb2, 0xaf, 0xaf, 0xb2, 0xba, 0xcf, 0x4c,
-0x34, 0x29, 0x22, 0x1e, 0x1d, 0x1e, 0x22, 0x2b, 0x3e, 0xc7,
-0xab, 0x9f, 0x99, 0x96, 0x95, 0x96, 0x9a, 0xa2, 0xb5, 0x4a,
-0x28, 0x1c, 0x15, 0x11, 0x10, 0x12, 0x17, 0x1e, 0x2e, 0xd5,
-0xa9, 0x9b, 0x95, 0x90, 0x8f, 0x91, 0x96, 0x9d, 0xac, 0x78,
-0x2c, 0x1e, 0x17, 0x13, 0x12, 0x14, 0x18, 0x1f, 0x2d, 0x5d,
-0xb3, 0xa4, 0x9d, 0x9a, 0x99, 0x9b, 0x9e, 0xa6, 0xb2, 0xd6,
-0x3f, 0x2f, 0x29, 0x26, 0x26, 0x28, 0x2d, 0x35, 0x42, 0x5e,
-0xd8, 0xca, 0xc6, 0xc9, 0xcf, 0xe4, 0x69, 0x59, 0x58, 0x64,
-0xdf, 0xc7, 0xba, 0xb1, 0xac, 0xaa, 0xaa, 0xad, 0xb4, 0xc7,
-0x4f, 0x33, 0x27, 0x1f, 0x1c, 0x1b, 0x1c, 0x1f, 0x27, 0x39,
-0xce, 0xac, 0x9f, 0x99, 0x95, 0x94, 0x95, 0x99, 0x9f, 0xaf,
-0x59, 0x2a, 0x1c, 0x16, 0x11, 0x10, 0x11, 0x16, 0x1d, 0x2b };
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 712bdc7cb46ad71adfe420a811f45ef3ca310ca8..69b5fdfde55a77d8b5ee23e7388fddd045a8f671 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -337,14 +337,6 @@ static int check_for_rtp_changes(struct ast_channel *chan, struct ast_rtp_instan
 		ast_sockaddr_setnull(&media->direct_media_addr);
 		changed = 1;
 		if (media->rtp) {
-			/* Direct media has ended - reset time of last received RTP packet
-			 * to avoid premature RTP timeout. Synchronisation between the
-			 * modification of direct_mdedia_addr+last_rx here and reading the
-			 * values in res_pjsip_sdp_rtp.c:rtp_check_timeout() is provided
-			 * by the channel's lock (which is held while this function is
-			 * executed).
-			 */
-			ast_rtp_instance_set_last_rx(media->rtp, time(NULL));
 			ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 1);
 			if (position != -1) {
 				ast_channel_set_fd(chan, position + AST_EXTENDED_FDS, ast_rtp_instance_fd(media->rtp, 1));
@@ -976,7 +968,7 @@ static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, stru
 			struct ast_str *write_transpath = ast_str_alloca(256);
 			struct ast_str *read_transpath = ast_str_alloca(256);
 
-			ast_debug(3,
+			ast_log(LOG_WARNING,
 				"Channel %s asked to send %s frame when native formats are %s (rd:%s->%s;%s wr:%s->%s;%s)\n",
 				ast_channel_name(ast),
 				ast_format_get_name(frame->subclass.format),
@@ -1301,7 +1293,7 @@ static const char *chan_pjsip_get_uniqueid(struct ast_channel *ast)
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
 	char *uniqueid = ast_threadstorage_get(&uniqueid_threadbuf, UNIQUEID_BUFSIZE);
 
-	if (!uniqueid) {
+	if (!channel || !uniqueid) {
 		return "";
 	}
 
@@ -1499,18 +1491,13 @@ static int update_connected_line_information(void *data)
 	return 0;
 }
 
-/*! \brief Callback which changes the value of locally held on the media stream */
-static void local_hold_set_state(struct ast_sip_session_media *session_media, unsigned int held)
+/*! \brief Update local hold state and send a re-INVITE with the new SDP */
+static int remote_send_hold_refresh(struct ast_sip_session *session, unsigned int held)
 {
+	struct ast_sip_session_media *session_media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
 	if (session_media) {
 		session_media->locally_held = held;
 	}
-}
-
-/*! \brief Update local hold state and send a re-INVITE with the new SDP */
-static int remote_send_hold_refresh(struct ast_sip_session *session, unsigned int held)
-{
-	AST_VECTOR_CALLBACK_VOID(&session->active_media_state->sessions, local_hold_set_state, held);
 	ast_sip_session_refresh(session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, NULL);
 	ao2_ref(session, -1);
 
@@ -1629,6 +1616,10 @@ static int handle_topology_request_change(struct ast_sip_session *session,
 	SCOPE_EXIT_RTN_VALUE(res, "RC: %d\n", res);
 }
 
+/* Forward declarations */
+static int transmit_info_dtmf(void *data);
+static struct info_dtmf_data *info_dtmf_data_alloc(struct ast_sip_session *session, char digit, unsigned int duration);
+
 /*! \brief Function called by core to ask the channel to indicate some sort of condition */
 static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
 {
@@ -1649,6 +1640,10 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
 		.data.ptr = (void *)data,
 	};
 	char condition_name[256];
+	unsigned int duration;
+	char digit;
+	struct info_dtmf_data *dtmf_data;
+
 	SCOPE_ENTER(3, "%s: Indicated %s\n", ast_channel_name(ast),
 		ast_frame_subclass2str(&f, condition_name, sizeof(condition_name), NULL, 0));
 
@@ -1659,8 +1654,12 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
 			if (channel->session->endpoint->inband_progress ||
 				(channel->session->inv_session && channel->session->inv_session->neg &&
 				pjmedia_sdp_neg_get_state(channel->session->inv_session->neg) == PJMEDIA_SDP_NEG_STATE_DONE)) {
-				response_code = 183;
 				res = -1;
+				if (ast_sip_get_allow_sending_180_after_183()) {
+					response_code = 180;
+				} else {
+					response_code = 183;
+				}
 			} else {
 				response_code = 180;
                          if (condition == AST_CONTROL_RINGING_CW)
@@ -1709,6 +1708,22 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
 		}
 		ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_sorcery_object_get_id(channel->session->endpoint));
 		break;
+	case AST_CONTROL_FLASH:
+		duration = 300;
+		digit = '!';
+		dtmf_data = info_dtmf_data_alloc(channel->session, digit, duration);
+
+		if (!dtmf_data) {
+			res = -1;
+			break;
+		}
+
+		if (ast_sip_push_task(channel->session->serializer, transmit_info_dtmf, dtmf_data)) {
+			ast_log(LOG_WARNING, "Error sending FLASH via INFO on channel %s\n", ast_channel_name(ast));
+			ao2_ref(dtmf_data, -1); /* dtmf_data can't be null here */
+			res = -1;
+		}
+		break;
 	case AST_CONTROL_VIDUPDATE:
 		for (i = 0; i < AST_VECTOR_SIZE(&channel->session->active_media_state->sessions); ++i) {
 			media = AST_VECTOR_GET(&channel->session->active_media_state->sessions, i);
@@ -2644,6 +2659,15 @@ static int hangup(void *data)
 		if (session) {
 			int cause = h_data->cause;
 
+			if (channel->session->active_media_state &&
+				channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]) {
+				struct ast_sip_session_media *media =
+					channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
+				if (media->rtp) {
+					ast_rtp_instance_set_stats_vars(ast, media->rtp);
+				}
+			}
+
 			/*
 	 		* It's possible that session_terminate might cause the session to be destroyed
 	 		* immediately so we need to keep a reference to it so we can NULL session->channel
@@ -3008,97 +3032,6 @@ static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text)
 	return rc;
 }
 
-/*! \brief Convert SIP hangup causes to Asterisk hangup causes */
-static int hangup_sip2cause(int cause)
-{
-	/* Possible values taken from causes.h */
-
-	switch(cause) {
-	case 401:       /* Unauthorized */
-		return AST_CAUSE_CALL_REJECTED;
-	case 403:       /* Not found */
-		return AST_CAUSE_CALL_REJECTED;
-	case 404:       /* Not found */
-		return AST_CAUSE_UNALLOCATED;
-	case 405:       /* Method not allowed */
-		return AST_CAUSE_INTERWORKING;
-	case 407:       /* Proxy authentication required */
-		return AST_CAUSE_CALL_REJECTED;
-	case 408:       /* No reaction */
-		return AST_CAUSE_NO_USER_RESPONSE;
-	case 409:       /* Conflict */
-		return AST_CAUSE_NORMAL_TEMPORARY_FAILURE;
-	case 410:       /* Gone */
-		return AST_CAUSE_NUMBER_CHANGED;
-	case 411:       /* Length required */
-		return AST_CAUSE_INTERWORKING;
-	case 413:       /* Request entity too large */
-		return AST_CAUSE_INTERWORKING;
-	case 414:       /* Request URI too large */
-		return AST_CAUSE_INTERWORKING;
-	case 415:       /* Unsupported media type */
-		return AST_CAUSE_INTERWORKING;
-	case 420:       /* Bad extension */
-		return AST_CAUSE_NO_ROUTE_DESTINATION;
-	case 480:       /* No answer */
-		return AST_CAUSE_NO_ANSWER;
-	case 481:       /* No answer */
-		return AST_CAUSE_INTERWORKING;
-	case 482:       /* Loop detected */
-		return AST_CAUSE_INTERWORKING;
-	case 483:       /* Too many hops */
-		return AST_CAUSE_NO_ANSWER;
-	case 484:       /* Address incomplete */
-		return AST_CAUSE_INVALID_NUMBER_FORMAT;
-	case 485:       /* Ambiguous */
-		return AST_CAUSE_UNALLOCATED;
-	case 486:       /* Busy everywhere */
-		return AST_CAUSE_BUSY;
-	case 487:       /* Request terminated */
-		return AST_CAUSE_INTERWORKING;
-	case 488:       /* No codecs approved */
-		return AST_CAUSE_BEARERCAPABILITY_NOTAVAIL;
-	case 491:       /* Request pending */
-		return AST_CAUSE_INTERWORKING;
-	case 493:       /* Undecipherable */
-		return AST_CAUSE_INTERWORKING;
-	case 500:       /* Server internal failure */
-		return AST_CAUSE_FAILURE;
-	case 501:       /* Call rejected */
-		return AST_CAUSE_FACILITY_REJECTED;
-	case 502:
-		return AST_CAUSE_DESTINATION_OUT_OF_ORDER;
-	case 503:       /* Service unavailable */
-		return AST_CAUSE_CONGESTION;
-	case 504:       /* Gateway timeout */
-		return AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE;
-	case 505:       /* SIP version not supported */
-		return AST_CAUSE_INTERWORKING;
-	case 600:       /* Busy everywhere */
-		return AST_CAUSE_USER_BUSY;
-	case 603:       /* Decline */
-		return AST_CAUSE_CALL_REJECTED;
-	case 604:       /* Does not exist anywhere */
-		return AST_CAUSE_UNALLOCATED;
-	case 606:       /* Not acceptable */
-		return AST_CAUSE_BEARERCAPABILITY_NOTAVAIL;
-	default:
-		if (cause < 500 && cause >= 400) {
-			/* 4xx class error that is unknown - someting wrong with our request */
-			return AST_CAUSE_INTERWORKING;
-		} else if (cause < 600 && cause >= 500) {
-			/* 5xx class error - problem in the remote end */
-			return AST_CAUSE_CONGESTION;
-		} else if (cause < 700 && cause >= 600) {
-			/* 6xx - global errors in the 4xx class */
-			return AST_CAUSE_INTERWORKING;
-		}
-		return AST_CAUSE_NORMAL;
-	}
-	/* Never reached */
-	return 0;
-}
-
 static void chan_pjsip_session_begin(struct ast_sip_session *session)
 {
 	RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
@@ -3129,11 +3062,21 @@ static void chan_pjsip_session_end(struct ast_sip_session *session)
 		SCOPE_EXIT_RTN("No channel\n");
 	}
 
+
+	if (session->active_media_state &&
+		session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]) {
+		struct ast_sip_session_media *media =
+			session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
+		if (media->rtp) {
+			ast_rtp_instance_set_stats_vars(session->channel, media->rtp);
+		}
+	}
+
 	chan_pjsip_remove_hold(ast_channel_uniqueid(session->channel));
 
 	ast_set_hangupsource(session->channel, ast_channel_name(session->channel), 0);
 	if (!ast_channel_hangupcause(session->channel) && session->inv_session) {
-		int cause = hangup_sip2cause(session->inv_session->cause);
+		int cause = ast_sip_hangup_sip2cause(session->inv_session->cause);
 
 		ast_queue_hangup_with_cause(session->channel, cause);
 	} else {
@@ -3145,11 +3088,11 @@ static void chan_pjsip_session_end(struct ast_sip_session *session)
 
 static void set_sipdomain_variable(struct ast_sip_session *session)
 {
-	pjsip_sip_uri *sip_ruri = pjsip_uri_get_uri(session->request_uri);
-	size_t size = pj_strlen(&sip_ruri->host) + 1;
+	const pj_str_t *host = ast_sip_pjsip_uri_get_hostname(session->request_uri);
+	size_t size = pj_strlen(host) + 1;
 	char *domain = ast_alloca(size);
 
-	ast_copy_pj_str(domain, &sip_ruri->host, size);
+	ast_copy_pj_str(domain, host, size);
 
 	pbx_builtin_setvar_helper(session->channel, "SIPDOMAIN", domain);
 	return;
@@ -3354,7 +3297,7 @@ static void chan_pjsip_incoming_response_update_cause(struct ast_sip_session *se
 	snprintf(cause_code->code, data_size - sizeof(*cause_code) + 1, "SIP %d %.*s", status.code,
 	(int) pj_strlen(&status.reason), pj_strbuf(&status.reason));
 
-	cause_code->ast_cause = hangup_sip2cause(status.code);
+	cause_code->ast_cause = ast_sip_hangup_sip2cause(status.code);
 	ast_queue_control_data(session->channel, AST_CONTROL_PVT_CAUSE_CODE, cause_code, data_size);
 	ast_channel_hangupcause_hash_set(session->channel, cause_code, data_size);
 
@@ -3366,6 +3309,7 @@ static void chan_pjsip_incoming_response(struct ast_sip_session *session, struct
 {
 	struct pjsip_status_line status = rdata->msg_info.msg->line.status;
 	SCOPE_ENTER(3, "%s: Status: %d\n", ast_sip_session_get_name(session), status.code);
+
 	if (!session->channel) {
 		SCOPE_EXIT_RTN("%s: No channel\n", ast_sip_session_get_name(session));
 	}
@@ -3405,6 +3349,7 @@ static void chan_pjsip_incoming_response(struct ast_sip_session *session, struct
 				ast_queue_control(session->channel, AST_CONTROL_PROGRESS);
 			}
 		} else {
+			ast_trace(-1, "%s: Queueing PROGRESS\n", ast_sip_session_get_name(session));
 			ast_trace(1, "%s Method: %.*s Status: %d  Queueing PROGRESS without SDP\n", ast_sip_session_get_name(session),
 				(int)rdata->msg_info.cseq->method.name.slen, rdata->msg_info.cseq->method.name.ptr, status.code);
 			if(!strcmp(session->endpoint->earlymedia,"sendrecv") || !strcmp(session->endpoint->earlymedia,"sendonly"))
diff --git a/channels/chan_rtp.c b/channels/chan_rtp.c
index 087ddaed3270697e28ff0fee76f63fb8de2ca123..0740c2c6a16d12df327fee655c436f88ae7f1621 100644
--- a/channels/chan_rtp.c
+++ b/channels/chan_rtp.c
@@ -381,9 +381,9 @@ static struct ast_channel *unicast_rtp_request(const char *type, struct ast_form
 
 	ast_channel_tech_pvt_set(chan, instance);
 
+	ast_rtp_instance_get_local_address(instance, &local_address);
 	pbx_builtin_setvar_helper(chan, "UNICASTRTP_LOCAL_ADDRESS",
 		ast_sockaddr_stringify_addr(&local_address));
-	ast_rtp_instance_get_local_address(instance, &local_address);
 	pbx_builtin_setvar_helper(chan, "UNICASTRTP_LOCAL_PORT",
 		ast_sockaddr_stringify_port(&local_address));
 
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index beb512b2ab4130432dcf079f2f375b58b76fa719..bf03c5ff268f9e1534b281c7a5ebca8e3db29922 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -176,7 +176,9 @@
 /*** MODULEINFO
 	<use type="module">res_crypto</use>
 	<use type="module">res_http_websocket</use>
+	<defaultenabled>no</defaultenabled>
 	<support_level>deprecated</support_level>
+	<replacement>chan_pjsip</replacement>
 	<deprecated_in>17</deprecated_in>
 	<removed_in>21</removed_in>
  ***/
@@ -12441,9 +12443,8 @@ static int reqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, ui
 	 * Send UPDATE to the same destination as CANCEL, if call is not in final state.
 	 */
 	if (!sip_route_empty(&p->route) &&
-			!(sipmethod == SIP_CANCEL ||
-				(sipmethod == SIP_ACK && (p->invitestate == INV_COMPLETED || p->invitestate == INV_CANCELLED)) ||
-				(sipmethod == SIP_UPDATE && (p->invitestate == INV_PROCEEDING || p->invitestate == INV_EARLY_MEDIA)))) {
+		!(sipmethod == SIP_CANCEL ||
+			(sipmethod == SIP_ACK && (p->invitestate == INV_COMPLETED || p->invitestate == INV_CANCELLED)))) {
 		if (p->socket.type != AST_TRANSPORT_UDP && p->socket.tcptls_session) {
 			/* For TCP/TLS sockets that are connected we won't need
 			 * to do any hostname/IP lookups */
@@ -12451,6 +12452,11 @@ static int reqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, ui
 			/* For NATed traffic, we ignore the contact/route and
 			 * simply send to the received-from address. No need
 			 * for lookups. */
+		} else if (sipmethod == SIP_UPDATE && (p->invitestate == INV_PROCEEDING || p->invitestate == INV_EARLY_MEDIA)) {
+			/* Calling set_destination for an UPDATE in early dialog
+			 * will result in mangling of the target for a subsequent
+			 * CANCEL according to ASTERISK-24628 so do not do it.
+			 */
 		} else {
 			set_destination(p, sip_route_first_uri(&p->route));
 		}
@@ -12501,11 +12507,9 @@ static int reqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, ui
 	   An exception to this behavior is the ACK request. Since Asterisk never requires
 	   session-timers support from a remote end-point (UAS) in an INVITE, it must
 	   not send 'Require: timer' header in the ACK request.
-	   This should only be added in the INVITE transactions, not MESSAGE or REFER or other
-	   in-dialog messages.
 	*/
 	if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_active_peer_ua == TRUE
-	    && sipmethod == SIP_INVITE) {
+	    && (sipmethod == SIP_INVITE || sipmethod == SIP_UPDATE)) {
 		char se_hdr[256];
 		snprintf(se_hdr, sizeof(se_hdr), "%d;refresher=%s", p->stimer->st_interval,
 			p->stimer->st_ref == SESSION_TIMER_REFRESHER_US ? "uac" : "uas");
@@ -35416,8 +35420,8 @@ AST_TEST_DEFINE(get_in_brackets_const_test)
 			ast_test_status_update(test, "Unexpected result: %d != %d\n", expected_res, res); \
 			return AST_TEST_FAIL;				\
 		}							\
-		if ((expected_start) != start) {			\
-			const char *e = expected_start ? expected_start : "(null)"; \
+		if ((void *)(expected_start) != (void *)start) {			\
+			const char *e = ((void *)expected_start != (void *)NULL) ? expected_start : "(null)"; \
 			const char *a = start ? start : "(null)";	\
 			ast_test_status_update(test, "Unexpected start: %s != %s\n", e, a); \
 			return AST_TEST_FAIL;				\
diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c
index 9b59075a70485f2426a7127ecec084e1937873fa..e3a7021793925a1e239c8642f8219f0453528147 100644
--- a/channels/chan_skinny.c
+++ b/channels/chan_skinny.c
@@ -34,7 +34,8 @@
  */
 
 /*** MODULEINFO
-	<support_level>extended</support_level>
+	<defaultenabled>no</defaultenabled>
+	<support_level>deprecated</support_level>
 	<deprecated_in>19</deprecated_in>
 	<removed_in>21</removed_in>
  ***/
@@ -8845,7 +8846,7 @@ static int reload(void)
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Skinny Client Control Protocol (Skinny)",
-	.support_level = AST_MODULE_SUPPORT_EXTENDED,
+	.support_level = AST_MODULE_SUPPORT_DEPRECATED,
 	.load = load_module,
 	.unload = unload_module,
 	.reload = reload,
diff --git a/channels/chan_vpb.cc b/channels/chan_vpb.cc
deleted file mode 100644
index 9bcf3059ec5be4d29b2843b0b7692cb1bf5ee17f..0000000000000000000000000000000000000000
--- a/channels/chan_vpb.cc
+++ /dev/null
@@ -1,2882 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2003, Paul Bagyenda
- * Paul Bagyenda <bagyenda@dsmagic.com>
- * Copyright (C) 2004 - 2005, Ben Kramer
- * Ben Kramer <ben@voicetronix.com.au>
- *
- * Daniel Bichara <daniel@bichara.com.br> - Brazilian CallerID detection (c)2004
- *
- * Welber Silveira - welberms@magiclink.com.br - (c)2004
- * Copying CLID string to propper structure after detection
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief VoiceTronix Interface driver
- *
- * \ingroup channel_drivers
- */
-
-/*! \li \ref chan_vpb.cc uses the configuration file \ref vpb.conf
- * \addtogroup configuration_file
- */
-
-/*! \page vpb.conf vpb.conf
- * \verbinclude vpb.conf.sample
- */
-
-/*
- * XXX chan_vpb needs its native bridge code converted to the
- * new bridge technology scheme.  The chan_dahdi native bridge
- * code can be used as an example.  It is unlikely that this
- * will ever get done.
- *
- * The existing native bridge code is marked with the
- * VPB_NATIVE_BRIDGING conditional.
- */
-
-/*** MODULEINFO
-	<depend>vpb</depend>
-	<defaultenabled>no</defaultenabled>
-	<support_level>deprecated</support_level>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include <vpbapi.h>
-
-extern "C" {
-
-#include "asterisk.h"
-
-#include "asterisk/lock.h"
-#include "asterisk/utils.h"
-#include "asterisk/channel.h"
-#include "asterisk/config.h"
-#include "asterisk/module.h"
-#include "asterisk/pbx.h"
-#include "asterisk/callerid.h"
-#include "asterisk/dsp.h"
-#include "asterisk/features.h"
-#include "asterisk/musiconhold.h"
-#include "asterisk/format_cache.h"
-}
-
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <arpa/inet.h>
-#include <fcntl.h>
-#include <sys/ioctl.h>
-#include <ctype.h>
-
-#include <assert.h>
-
-#ifdef pthread_create
-#undef pthread_create
-#endif
-
-#define DEFAULT_GAIN 0
-#define DEFAULT_ECHO_CANCEL 1
-
-#define VPB_SAMPLES 160
-#define VPB_MAX_BUF VPB_SAMPLES*4 + AST_FRIENDLY_OFFSET
-
-#define VPB_NULL_EVENT 200
-
-#define VPB_WAIT_TIMEOUT 4000
-
-#define MAX_VPB_GAIN 12.0
-#define MIN_VPB_GAIN -12.0
-
-#define DTMF_CALLERID
-#define DTMF_CID_START 'D'
-#define DTMF_CID_STOP 'C'
-
-/**/
-#if defined(__cplusplus) || defined(c_plusplus)
- extern "C" {
-#endif
-/**/
-
-static const char tdesc[] = "Standard VoiceTronix API Driver";
-static const char config[] = "vpb.conf";
-
-/* Default context for dialtone mode */
-static char context[AST_MAX_EXTENSION] = "default";
-
-/* Default language */
-static char language[MAX_LANGUAGE] = "";
-
-static int gruntdetect_timeout = 3600000; /* Grunt detect timeout is 1hr. */
-
-/* Protect the interface list (of vpb_pvt's) */
-AST_MUTEX_DEFINE_STATIC(iflock);
-
-/* Protect the monitoring thread, so only one process can kill or start it, and not
-   when it's doing something critical. */
-AST_MUTEX_DEFINE_STATIC(monlock);
-
-/* This is the thread for the monitor which checks for input on the channels
-   which are not currently in use.  */
-static pthread_t monitor_thread;
-
-static int mthreadactive = -1; /* Flag for monitoring monitorthread.*/
-
-
-static int restart_monitor(void);
-
-/* The private structures of the VPB channels are
-   linked for selecting outgoing channels */
-
-#define MODE_DIALTONE 	1
-#define MODE_IMMEDIATE	2
-#define MODE_FXO	3
-
-/* Pick a country or add your own! */
-/* These are the tones that are played to the user */
-#define TONES_AU
-/* #define TONES_USA */
-
-#ifdef TONES_AU
-static VPB_TONE Dialtone     = {440,   	440, 	440, 	-10,  	-10, 	-10, 	5000,	0   };
-static VPB_TONE Busytone     = {470,   	0,   	0, 	-10,  	-100, 	-100,   5000, 	0 };
-static VPB_TONE Ringbacktone = {400,   	50,   	440, 	-10,  	-10, 	-10,  	1400, 	800 };
-#endif
-#ifdef TONES_USA
-static VPB_TONE Dialtone     = {350, 440,   0, -16,   -16, -100, 10000,    0};
-static VPB_TONE Busytone     = {480, 620,   0, -10,   -10, -100,   500,  500};
-static VPB_TONE Ringbacktone = {440, 480,   0, -20,   -20, -100,  2000, 4000};
-#endif
-
-/* grunt tone defn's */
-#if 0
-static VPB_DETECT toned_grunt = { 3, VPB_GRUNT, 1, 2000, 3000, 0, 0, -40, 0, 0, 0, 40, { { VPB_DELAY, 1000, 0, 0 }, { VPB_RISING, 0, 40, 0 }, { 0, 100, 0, 0 } } };
-#endif
-static VPB_DETECT toned_ungrunt = { 2, VPB_GRUNT, 1, 2000, 1, 0, 0, -40, 0, 0, 30, 40, { { 0, 0, 0, 0 } } };
-
-/* Use loop polarity detection for CID */
-static int UsePolarityCID=0;
-
-/* Use loop drop detection */
-static int UseLoopDrop=1;
-
-/* To use or not to use Native bridging */
-static int UseNativeBridge=1;
-
-/* Use Asterisk Indication or VPB */
-static int use_ast_ind=0;
-
-/* Use Asterisk DTMF detection or VPB */
-static int use_ast_dtmfdet=0;
-
-static int relaxdtmf=0;
-
-/* Use Asterisk DTMF play back or VPB */
-static int use_ast_dtmf=0;
-
-/* Break for DTMF on native bridge ? */
-static int break_for_dtmf=1;
-
-/* Set EC suppression threshold */
-static short ec_supp_threshold=-1;
-
-/* Inter Digit Delay for collecting DTMF's */
-static int dtmf_idd = 3000;
-
-#define TIMER_PERIOD_RINGBACK 2000
-#define TIMER_PERIOD_BUSY 700
-#define TIMER_PERIOD_RING 4000
-static int timer_period_ring = TIMER_PERIOD_RING;
-
-#define VPB_EVENTS_ALL (VPB_MRING|VPB_MDIGIT|VPB_MDTMF|VPB_MTONEDETECT|VPB_MTIMEREXP \
-			|VPB_MSTATION_OFFHOOK|VPB_MSTATION_ONHOOK \
-			|VPB_MRING_OFF|VPB_MDROP|VPB_MSTATION_FLASH)
-#define VPB_EVENTS_NODROP (VPB_MRING|VPB_MDIGIT|VPB_MDTMF|VPB_MTONEDETECT|VPB_MTIMEREXP \
-			|VPB_MSTATION_OFFHOOK|VPB_MSTATION_ONHOOK \
-			|VPB_MRING_OFF|VPB_MSTATION_FLASH)
-#define VPB_EVENTS_NODTMF (VPB_MRING|VPB_MDIGIT|VPB_MTONEDETECT|VPB_MTIMEREXP \
-			|VPB_MSTATION_OFFHOOK|VPB_MSTATION_ONHOOK \
-			|VPB_MRING_OFF|VPB_MDROP|VPB_MSTATION_FLASH)
-#define VPB_EVENTS_STAT (VPB_MRING|VPB_MDIGIT|VPB_MDTMF|VPB_MTONEDETECT|VPB_MTIMEREXP \
-			|VPB_MSTATION_OFFHOOK|VPB_MSTATION_ONHOOK \
-			|VPB_MRING_OFF|VPB_MSTATION_FLASH)
-
-
-/* Dialing parameters for Australia */
-/* #define DIAL_WITH_CALL_PROGRESS */
-VPB_TONE_MAP DialToneMap[] = { 	{ VPB_BUSY, VPB_CALL_DISCONNECT, 0 },
-  				{ VPB_DIAL, VPB_CALL_DIALTONE, 0 },
-				{ VPB_RINGBACK, VPB_CALL_RINGBACK, 0 },
-				{ VPB_BUSY, VPB_CALL_BUSY, 0 },
-				{ VPB_GRUNT, VPB_CALL_GRUNT, 0 },
-				{ 0, 0, 1 } };
-#define VPB_DIALTONE_WAIT 2000 /* Wait up to 2s for a dialtone */
-#define VPB_RINGWAIT 4000 /* Wait up to 4s for ring tone after dialing */
-#define VPB_CONNECTED_WAIT 4000 /* If no ring tone detected for 4s then consider call connected */
-#define TIMER_PERIOD_NOANSWER 120000 /* Let it ring for 120s before deciding theres noone there */
-
-#define MAX_BRIDGES_V4PCI 2
-#define MAX_BRIDGES_V12PCI 128
-
-/* port states */
-#define VPB_STATE_ONHOOK	0
-#define VPB_STATE_OFFHOOK	1
-#define VPB_STATE_DIALLING	2
-#define VPB_STATE_JOINED	3
-#define VPB_STATE_GETDTMF	4
-#define VPB_STATE_PLAYDIAL	5
-#define VPB_STATE_PLAYBUSY	6
-#define VPB_STATE_PLAYRING	7
-
-#define VPB_GOT_RXHWG		1
-#define VPB_GOT_TXHWG		2
-#define VPB_GOT_RXSWG		4
-#define VPB_GOT_TXSWG		8
-
-typedef struct  {
-	int inuse;
-	struct ast_channel *c0, *c1, **rc;
-	struct ast_frame **fo;
-	int flags;
-	ast_mutex_t lock;
-	ast_cond_t cond;
-	int endbridge;
-} vpb_bridge_t;
-
-static vpb_bridge_t * bridges;
-static int max_bridges = MAX_BRIDGES_V4PCI;
-
-AST_MUTEX_DEFINE_STATIC(bridge_lock);
-
-typedef enum {
-	vpb_model_unknown = 0,
-	vpb_model_v4pci,
-	vpb_model_v12pci
-} vpb_model_t;
-
-static struct vpb_pvt {
-
-	ast_mutex_t owner_lock;           /*!< Protect blocks that expect ownership to remain the same */
-	struct ast_channel *owner;        /*!< Channel who owns us, possibly NULL */
-
-	int golock;                       /*!< Got owner lock ? */
-
-	int mode;                         /*!< fxo/imediate/dialtone */
-	int handle;                       /*!< Handle for vpb interface */
-
-	int state;                        /*!< used to keep port state (internal to driver) */
-
-	int group;                        /*!< Which group this port belongs to */
-	ast_group_t callgroup;            /*!< Call group */
-	ast_group_t pickupgroup;          /*!< Pickup group */
-
-
-	char dev[256];                    /*!< Device name, eg vpb/1-1 */
-	vpb_model_t vpb_model;            /*!< card model */
-
-	struct ast_frame f, fr;           /*!< Asterisk frame interface */
-	char buf[VPB_MAX_BUF];            /*!< Static buffer for reading frames */
-
-	int dialtone;                     /*!< NOT USED */
-	float txgain, rxgain;             /*!< Hardware gain control */
-	float txswgain, rxswgain;         /*!< Software gain control */
-
-	int wantdtmf;                     /*!< Waiting for DTMF. */
-	char context[AST_MAX_EXTENSION];  /*!< The context for this channel */
-
-	char ext[AST_MAX_EXTENSION];      /*!< DTMF buffer for the ext[ens] */
-	char language[MAX_LANGUAGE];      /*!< language being used */
-	char callerid[AST_MAX_EXTENSION]; /*!< CallerId used for directly connected phone */
-	int  callerid_type;               /*!< Caller ID type: 0=>none 1=>vpb 2=>AstV23 3=>AstBell */
-	char cid_num[AST_MAX_EXTENSION];
-	char cid_name[AST_MAX_EXTENSION];
-
-	int dtmf_caller_pos;              /*!< DTMF CallerID detection (Brazil)*/
-
-	int lastoutput;                   /*!< Holds the last Audio format output'ed */
-	int lastinput;                    /*!< Holds the last Audio format input'ed */
-	int last_ignore_dtmf;
-
-	void *busy_timer;                 /*!< Void pointer for busy vpb_timer */
-	int busy_timer_id;                /*!< unique timer ID for busy timer */
-
-	void *ringback_timer;             /*!< Void pointer for ringback vpb_timer */
-	int ringback_timer_id;            /*!< unique timer ID for ringback timer */
-
-	void *ring_timer;                 /*!< Void pointer for ring vpb_timer */
-	int ring_timer_id;                /*!< unique timer ID for ring timer */
-
-	void *dtmfidd_timer;              /*!< Void pointer for DTMF IDD vpb_timer */
-	int dtmfidd_timer_id;             /*!< unique timer ID for DTMF IDD timer */
-
-	struct ast_dsp *vad;              /*!< AST  Voice Activation Detection dsp */
-
-	struct timeval lastgrunt;         /*!< time stamp of last grunt event */
-
-	ast_mutex_t lock;                 /*!< This one just protects bridge ptr below */
-	vpb_bridge_t *bridge;
-
-	int stopreads;                    /*!< Stop reading...*/
-	int read_state;                   /*!< Read state */
-	int chuck_count;                  /*!< a count of packets weve chucked away!*/
-	pthread_t readthread;             /*!< For monitoring read channel. One per owned channel. */
-
-	ast_mutex_t record_lock;          /*!< This one prevents reentering a record_buf block */
-	ast_mutex_t play_lock;            /*!< This one prevents reentering a play_buf block */
-	int  play_buf_time;               /*!< How long the last play_buf took */
-	struct timeval lastplay;          /*!< Last play time */
-
-	ast_mutex_t play_dtmf_lock;
-	char play_dtmf[16];
-
-	int faxhandled;                   /*!< has a fax tone been handled ? */
-
-	struct vpb_pvt *next;             /*!< Next channel in list */
-
-} *iflist = NULL;
-
-static struct ast_channel *vpb_new(struct vpb_pvt *i, enum ast_channel_state state, const char *context, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor);
-static void *do_chanreads(void *pvt);
-static struct ast_channel *vpb_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause);
-static int vpb_digit_begin(struct ast_channel *ast, char digit);
-static int vpb_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
-static int vpb_call(struct ast_channel *ast, const char *dest, int timeout);
-static int vpb_hangup(struct ast_channel *ast);
-static int vpb_answer(struct ast_channel *ast);
-static struct ast_frame *vpb_read(struct ast_channel *ast);
-static int vpb_write(struct ast_channel *ast, struct ast_frame *frame);
-static int vpb_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
-static int vpb_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
-
-static struct ast_channel_tech vpb_tech = {
-	.type = "vpb",
-	.description = tdesc,
-	.capabilities = NULL,
-	.properties = 0,
-	.requester = vpb_request,
-	.requester_with_stream_topology = NULL,
-	.devicestate = NULL,
-	.presencestate = NULL,
-	.send_digit_begin = vpb_digit_begin,
-	.send_digit_end = vpb_digit_end,
-	.call = vpb_call,
-	.hangup = vpb_hangup,
-	.answer = vpb_answer,
-	.answer_with_stream_topology = NULL,
-	.read = vpb_read,
-	.read_stream = NULL,
-	.write = vpb_write,
-	.write_stream = NULL,
-	.send_text = NULL,
-	.send_image = NULL,
-	.send_html = NULL,
-	.exception = NULL,
-	.early_bridge = NULL,
-	.indicate = vpb_indicate,
-	.fixup = vpb_fixup,
-	.setoption = NULL,
-	.queryoption = NULL,
-	.transfer = NULL,
-	.write_video = NULL,
-	.write_text = NULL,
-	.func_channel_read = NULL,
-	.func_channel_write = NULL,
-};
-
-static struct ast_channel_tech vpb_tech_indicate = {
-	.type = "vpb",
-	.description = tdesc,
-	.capabilities = NULL,
-	.properties = 0,
-	.requester = vpb_request,
-	.requester_with_stream_topology = NULL,
-	.devicestate = NULL,
-	.presencestate = NULL,
-	.send_digit_begin = vpb_digit_begin,
-	.send_digit_end = vpb_digit_end,
-	.call = vpb_call,
-	.hangup = vpb_hangup,
-	.answer = vpb_answer,
-	.answer_with_stream_topology = NULL,
-	.read = vpb_read,
-	.read_stream = NULL,
-	.write = vpb_write,
-	.write_stream = NULL,
-	.send_text = NULL,
-	.send_image = NULL,
-	.send_html = NULL,
-	.exception = NULL,
-	.early_bridge = NULL,
-	.indicate = NULL,
-	.fixup = vpb_fixup,
-	.setoption = NULL,
-	.queryoption = NULL,
-	.transfer = NULL,
-	.write_video = NULL,
-	.write_text = NULL,
-	.func_channel_read = NULL,
-	.func_channel_write = NULL,
-};
-
-#if defined(VPB_NATIVE_BRIDGING)
-/* Can't get ast_vpb_bridge() working on v4pci without either a horrible
-*  high pitched feedback noise or bad hiss noise depending on gain settings
-*  Get asterisk to do the bridging
-*/
-#define BAD_V4PCI_BRIDGE
-
-/* This one enables a half duplex bridge which may be required to prevent high pitched
- * feedback when getting asterisk to do the bridging and when using certain gain settings.
- */
-/* #define HALF_DUPLEX_BRIDGE */
-
-/* This is the Native bridge code, which Asterisk will try before using its own bridging code */
-static enum ast_bridge_result ast_vpb_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc, int timeoutms)
-{
-	struct vpb_pvt *p0 = (struct vpb_pvt *)ast_channel_tech_pvt(c0);
-	struct vpb_pvt *p1 = (struct vpb_pvt *)ast_channel_tech_pvt(c1);
-	int i;
-	int res;
-	struct ast_channel *cs[3];
-	struct ast_channel *who;
-	struct ast_frame *f;
-
-	cs[0] = c0;
-	cs[1] = c1;
-
-	#ifdef BAD_V4PCI_BRIDGE
-	if (p0->vpb_model == vpb_model_v4pci)
-		return AST_BRIDGE_FAILED_NOWARN;
-	#endif
-	if (UseNativeBridge != 1) {
-		return AST_BRIDGE_FAILED_NOWARN;
-	}
-
-/*
-	ast_mutex_lock(&p0->lock);
-	ast_mutex_lock(&p1->lock);
-*/
-
-	/* Bridge channels, check if we can.  I believe we always can, so find a slot.*/
-
-	ast_mutex_lock(&bridge_lock);
-	for (i = 0; i < max_bridges; i++)
-		if (!bridges[i].inuse)
-			break;
-	if (i < max_bridges) {
-		bridges[i].inuse = 1;
-		bridges[i].endbridge = 0;
-		bridges[i].flags = flags;
-		bridges[i].rc = rc;
-		bridges[i].fo = fo;
-		bridges[i].c0 = c0;
-		bridges[i].c1 = c1;
-	}
-	ast_mutex_unlock(&bridge_lock);
-
-	if (i == max_bridges) {
-		ast_log(LOG_WARNING, "%s: vpb_bridge: Failed to bridge %s and %s!\n", p0->dev, ast_channel_name(c0), ast_channel_name(c1));
-		ast_mutex_unlock(&p0->lock);
-		ast_mutex_unlock(&p1->lock);
-		return AST_BRIDGE_FAILED_NOWARN;
-	} else {
-		/* Set bridge pointers. You don't want to take these locks while holding bridge lock.*/
-		ast_mutex_lock(&p0->lock);
-		p0->bridge = &bridges[i];
-		ast_mutex_unlock(&p0->lock);
-
-		ast_mutex_lock(&p1->lock);
-		p1->bridge = &bridges[i];
-		ast_mutex_unlock(&p1->lock);
-
-		ast_verb(2, "%s: vpb_bridge: Bridging call entered with [%s, %s]\n", p0->dev, ast_channel_name(c0), ast_channel_name(c1));
-	}
-
-	ast_verb(3, "Native bridging %s and %s\n", ast_channel_name(c0), ast_channel_name(c1));
-
-	#ifdef HALF_DUPLEX_BRIDGE
-
-	ast_debug(2, "%s: vpb_bridge: Starting half-duplex bridge [%s, %s]\n", p0->dev, ast_channel_name(c0), ast_channel_name(c1));
-
-	int dir = 0;
-
-	memset(p0->buf, 0, sizeof(p0->buf));
-	memset(p1->buf, 0, sizeof(p1->buf));
-
-	vpb_record_buf_start(p0->handle, VPB_ALAW);
-	vpb_record_buf_start(p1->handle, VPB_ALAW);
-
-	vpb_play_buf_start(p0->handle, VPB_ALAW);
-	vpb_play_buf_start(p1->handle, VPB_ALAW);
-
-	while (!bridges[i].endbridge) {
-		struct vpb_pvt *from, *to;
-		if (++dir % 2) {
-			from = p0;
-			to = p1;
-		} else {
-			from = p1;
-			to = p0;
-		}
-		vpb_record_buf_sync(from->handle, from->buf, VPB_SAMPLES);
-		vpb_play_buf_sync(to->handle, from->buf, VPB_SAMPLES);
-	}
-
-	vpb_record_buf_finish(p0->handle);
-	vpb_record_buf_finish(p1->handle);
-
-	vpb_play_buf_finish(p0->handle);
-	vpb_play_buf_finish(p1->handle);
-
-	ast_debug(2, "%s: vpb_bridge: Finished half-duplex bridge [%s, %s]\n", p0->dev, ast_channel_name(c0), ast_channel_name(c1));
-
-	res = VPB_OK;
-
-	#else
-
-	res = vpb_bridge(p0->handle, p1->handle, VPB_BRIDGE_ON);
-	if (res == VPB_OK) {
-		/* pthread_cond_wait(&bridges[i].cond, &bridges[i].lock);*/ /* Wait for condition signal. */
-		while (!bridges[i].endbridge) {
-			/* Are we really ment to be doing nothing ?!?! */
-			who = ast_waitfor_n(cs, 2, &timeoutms);
-			if (!who) {
-				if (!timeoutms) {
-					res = AST_BRIDGE_RETRY;
-					break;
-				}
-				ast_debug(1, "%s: vpb_bridge: Empty frame read...\n", p0->dev);
-				/* check for hangup / whentohangup */
-				if (ast_check_hangup(c0) || ast_check_hangup(c1))
-					break;
-				continue;
-			}
-			f = ast_read(who);
-			if (!f || ((f->frametype == AST_FRAME_DTMF) &&
-					   (((who == c0) && (flags & AST_BRIDGE_DTMF_CHANNEL_0)) ||
-				       ((who == c1) && (flags & AST_BRIDGE_DTMF_CHANNEL_1))))) {
-				*fo = f;
-				*rc = who;
-				ast_debug(1, "%s: vpb_bridge: Got a [%s]\n", p0->dev, f ? "digit" : "hangup");
-#if 0
-				if ((c0->tech_pvt == pvt0) && (!ast_check_hangup(c0))) {
-					if (pr0->set_rtp_peer(c0, NULL, NULL, 0))
-						ast_log(LOG_WARNING, "Channel '%s' failed to revert\n", c0->name);
-				}
-				if ((c1->tech_pvt == pvt1) && (!ast_check_hangup(c1))) {
-					if (pr1->set_rtp_peer(c1, NULL, NULL, 0))
-						ast_log(LOG_WARNING, "Channel '%s' failed to revert back\n", c1->name);
-				}
-				/* That's all we needed */
-				return 0;
-#endif
-				/* Check if we need to break */
-				if (break_for_dtmf) {
-					break;
-				} else if ((f->frametype == AST_FRAME_DTMF) && ((f->subclass.integer == '#') || (f->subclass.integer == '*'))) {
-					break;
-				}
-			} else {
-				if ((f->frametype == AST_FRAME_DTMF) ||
-					(f->frametype == AST_FRAME_VOICE) ||
-					(f->frametype == AST_FRAME_VIDEO))
-					{
-					/* Forward voice or DTMF frames if they happen upon us */
-					/* Actually I dont think we want to forward on any frames!
-					if (who == c0) {
-						ast_write(c1, f);
-					} else if (who == c1) {
-						ast_write(c0, f);
-					}
-					*/
-				}
-				ast_frfree(f);
-			}
-			/* Swap priority not that it's a big deal at this point */
-			cs[2] = cs[0];
-			cs[0] = cs[1];
-			cs[1] = cs[2];
-		};
-		vpb_bridge(p0->handle, p1->handle, VPB_BRIDGE_OFF);
-	}
-
-	#endif
-
-	ast_mutex_lock(&bridge_lock);
-	bridges[i].inuse = 0;
-	ast_mutex_unlock(&bridge_lock);
-
-	p0->bridge = NULL;
-	p1->bridge = NULL;
-
-
-	ast_verb(2, "Bridging call done with [%s, %s] => %d\n", ast_channel_name(c0), ast_channel_name(c1), res);
-
-/*
-	ast_mutex_unlock(&p0->lock);
-	ast_mutex_unlock(&p1->lock);
-*/
-	return (res == VPB_OK) ? AST_BRIDGE_COMPLETE : AST_BRIDGE_FAILED;
-}
-#endif	/* defined(VPB_NATIVE_BRIDGING) */
-
-/* Caller ID can be located in different positions between the rings depending on your Telco
- * Australian (Telstra) callerid starts 700ms after 1st ring and finishes 1.5s after first ring
- * Use ANALYSE_CID to record rings and determine location of callerid
- */
-/* #define ANALYSE_CID */
-#define RING_SKIP 300
-#define CID_MSECS 2000
-
-static void get_callerid(struct vpb_pvt *p)
-{
-	short buf[CID_MSECS*8]; /* 8kHz sampling rate */
-	struct timeval cid_record_time;
-	int rc;
-	struct ast_channel *owner = p->owner;
-/*
-	char callerid[AST_MAX_EXTENSION] = "";
-*/
-#ifdef ANALYSE_CID
-	void * ws;
-	char * file="cidsams.wav";
-#endif
-
-
-	if (ast_mutex_trylock(&p->record_lock) == 0) {
-
-		cid_record_time = ast_tvnow();
-		ast_verb(4, "CID record - start\n");
-
-		/* Skip any trailing ringtone */
-		if (UsePolarityCID != 1){
-			vpb_sleep(RING_SKIP);
-		}
-
-		ast_verb(4, "CID record - skipped %lldms trailing ring\n",
-				 (long long int) ast_tvdiff_ms(ast_tvnow(), cid_record_time));
-		cid_record_time = ast_tvnow();
-
-		/* Record bit between the rings which contains the callerid */
-		vpb_record_buf_start(p->handle, VPB_LINEAR);
-		rc = vpb_record_buf_sync(p->handle, (char*)buf, sizeof(buf));
-		vpb_record_buf_finish(p->handle);
-#ifdef ANALYSE_CID
-		vpb_wave_open_write(&ws, file, VPB_LINEAR);
-		vpb_wave_write(ws, (char *)buf, sizeof(buf));
-		vpb_wave_close_write(ws);
-#endif
-
-		ast_verb(4, "CID record - recorded %lldms between rings\n",
-				 (long long int) ast_tvdiff_ms(ast_tvnow(), cid_record_time));
-
-		ast_mutex_unlock(&p->record_lock);
-
-		if (rc != VPB_OK) {
-			ast_log(LOG_ERROR, "Failed to record caller id sample on %s\n", p->dev);
-			return;
-		}
-
-		VPB_CID *cli_struct = new VPB_CID;
-		cli_struct->ra_cldn[0] = 0;
-		cli_struct->ra_cn[0] = 0;
-		/* This decodes FSK 1200baud type callerid */
-		if ((rc = vpb_cid_decode2(cli_struct, buf, CID_MSECS * 8)) == VPB_OK ) {
-			/*
-			if (owner->cid.cid_num)
-				ast_free(owner->cid.cid_num);
-			owner->cid.cid_num=NULL;
-			if (owner->cid.cid_name)
-				ast_free(owner->cid.cid_name);
-			owner->cid.cid_name=NULL;
-			*/
-
-			if (cli_struct->ra_cldn[0] == '\0') {
-				/*
-				owner->cid.cid_num = ast_strdup(cli_struct->cldn);
-				owner->cid.cid_name = ast_strdup(cli_struct->cn);
-				*/
-				if (owner) {
-					ast_set_callerid(owner, cli_struct->cldn, cli_struct->cn, cli_struct->cldn);
-				} else {
-					strcpy(p->cid_num, cli_struct->cldn);
-					strcpy(p->cid_name, cli_struct->cn);
-				}
-				ast_verb(4, "CID record - got [%s] [%s]\n",
-					S_COR(ast_channel_caller(owner)->id.number.valid, ast_channel_caller(owner)->id.number.str, ""),
-					S_COR(ast_channel_caller(owner)->id.name.valid, ast_channel_caller(owner)->id.name.str, ""));
-				snprintf(p->callerid, sizeof(p->callerid), "%s %s", cli_struct->cldn, cli_struct->cn);
-			} else {
-				ast_log(LOG_ERROR, "CID record - No caller id avalable on %s \n", p->dev);
-			}
-
-		} else {
-			ast_log(LOG_ERROR, "CID record - Failed to decode caller id on %s - %d\n", p->dev, rc);
-			ast_copy_string(p->callerid, "unknown", sizeof(p->callerid));
-		}
-		delete cli_struct;
-
-	} else
-		ast_log(LOG_ERROR, "CID record - Failed to set record mode for caller id on %s\n", p->dev);
-}
-
-static void get_callerid_ast(struct vpb_pvt *p)
-{
-	struct callerid_state *cs;
-	char buf[1024];
-	char *name = NULL, *number = NULL;
-	int flags;
-	int rc = 0, vrc;
-	int sam_count = 0;
-	struct ast_channel *owner = p->owner;
-	int which_cid;
-/*
-	float old_gain;
-*/
-#ifdef ANALYSE_CID
-	void * ws;
-	char * file = "cidsams.wav";
-#endif
-
-	if (p->callerid_type == 1) {
-		ast_verb(4, "Collected caller ID already\n");
-		return;
-	}
-	else if (p->callerid_type == 2 ) {
-		which_cid = CID_SIG_V23;
-		ast_verb(4, "Collecting Caller ID v23...\n");
-	}
-	else if (p->callerid_type == 3) {
-		which_cid = CID_SIG_BELL;
-		ast_verb(4, "Collecting Caller ID bell...\n");
-	} else {
-		ast_verb(4, "Caller ID disabled\n");
-		return;
-	}
-/*	vpb_sleep(RING_SKIP); */
-/*	vpb_record_get_gain(p->handle, &old_gain); */
-	cs = callerid_new(which_cid);
-	if (cs) {
-#ifdef ANALYSE_CID
-		vpb_wave_open_write(&ws, file, VPB_MULAW);
-		vpb_record_set_gain(p->handle, 3.0);
-		vpb_record_set_hw_gain(p->handle, 12.0);
-#endif
-		vpb_record_buf_start(p->handle, VPB_MULAW);
-		while ((rc == 0) && (sam_count < 8000 * 3)) {
-			vrc = vpb_record_buf_sync(p->handle, (char*)buf, sizeof(buf));
-			if (vrc != VPB_OK)
-				ast_log(LOG_ERROR, "%s: Caller ID couldn't read audio buffer!\n", p->dev);
-			rc = callerid_feed(cs, (unsigned char *)buf, sizeof(buf), ast_format_ulaw);
-#ifdef ANALYSE_CID
-			vpb_wave_write(ws, (char *)buf, sizeof(buf));
-#endif
-			sam_count += sizeof(buf);
-			ast_verb(4, "Collecting Caller ID samples [%d][%d]...\n", sam_count, rc);
-		}
-		vpb_record_buf_finish(p->handle);
-#ifdef ANALYSE_CID
-		vpb_wave_close_write(ws);
-#endif
-		if (rc == 1) {
-			callerid_get(cs, &name, &number, &flags);
-			ast_debug(1, "%s: Caller ID name [%s] number [%s] flags [%d]\n", p->dev, name, number, flags);
-		} else {
-			ast_log(LOG_ERROR, "%s: Failed to decode Caller ID \n", p->dev);
-		}
-/*		vpb_record_set_gain(p->handle, old_gain); */
-/*		vpb_record_set_hw_gain(p->handle,6.0); */
-	} else {
-		ast_log(LOG_ERROR, "%s: Failed to create Caller ID struct\n", p->dev);
-	}
-	ast_party_number_free(&ast_channel_caller(owner)->id.number);
-	ast_party_number_init(&ast_channel_caller(owner)->id.number);
-	ast_party_name_free(&ast_channel_caller(owner)->id.name);
-	ast_party_name_init(&ast_channel_caller(owner)->id.name);
-	if (number)
-		ast_shrink_phone_number(number);
-	ast_set_callerid(owner,
-		number, name,
-		ast_channel_caller(owner)->ani.number.valid ? NULL : number);
-	if (!ast_strlen_zero(name)){
-		snprintf(p->callerid, sizeof(p->callerid), "%s %s", number, name);
-	} else {
-		ast_copy_string(p->callerid, number, sizeof(p->callerid));
-	}
-	if (cs)
-		callerid_free(cs);
-}
-
-/* Terminate any tones we are presently playing */
-static void stoptone(int handle)
-{
-	int ret;
-	VPB_EVENT je;
-	while (vpb_playtone_state(handle) != VPB_OK) {
-		vpb_tone_terminate(handle);
-		ret = vpb_get_event_ch_async(handle, &je);
-		if ((ret == VPB_OK) && (je.type != VPB_DIALEND)) {
-			ast_verb(4, "Stop tone collected a wrong event!![%d]\n", je.type);
-/*			vpb_put_event(&je); */
-		}
-		vpb_sleep(10);
-	}
-}
-
-/* Safe vpb_playtone_async */
-static int playtone( int handle, VPB_TONE *tone)
-{
-	int ret = VPB_OK;
-	stoptone(handle);
-	ast_verb(4, "[%02d]: Playing tone\n", handle);
-	ret = vpb_playtone_async(handle, tone);
-	return ret;
-}
-
-static inline int monitor_handle_owned(struct vpb_pvt *p, VPB_EVENT *e)
-{
-	struct ast_frame f = {AST_FRAME_CONTROL}; /* default is control, Clear rest. */
-	int endbridge = 0;
-
-	ast_verb(4, "%s: handle_owned: got event: [%d=>%d]\n", p->dev, e->type, e->data);
-
-	f.src = "vpb";
-	switch (e->type) {
-	case VPB_RING:
-		if (p->mode == MODE_FXO) {
-			f.subclass.integer = AST_CONTROL_RING;
-			vpb_timer_stop(p->ring_timer);
-			vpb_timer_start(p->ring_timer);
-		} else
-			f.frametype = AST_FRAME_NULL; /* ignore ring on station port. */
-		break;
-
-	case VPB_RING_OFF:
-		f.frametype = AST_FRAME_NULL;
-		break;
-
-	case VPB_TIMEREXP:
-		if (e->data == p->busy_timer_id) {
-			playtone(p->handle, &Busytone);
-			p->state = VPB_STATE_PLAYBUSY;
-			vpb_timer_stop(p->busy_timer);
-			vpb_timer_start(p->busy_timer);
-			f.frametype = AST_FRAME_NULL;
-		} else if (e->data == p->ringback_timer_id) {
-			playtone(p->handle, &Ringbacktone);
-			vpb_timer_stop(p->ringback_timer);
-			vpb_timer_start(p->ringback_timer);
-			f.frametype = AST_FRAME_NULL;
-		} else if (e->data == p->ring_timer_id) {
-			/* We didnt get another ring in time! */
-			if (ast_channel_state(p->owner) != AST_STATE_UP)  {
-				 /* Assume caller has hung up */
-				vpb_timer_stop(p->ring_timer);
-				f.subclass.integer = AST_CONTROL_HANGUP;
-			} else {
-				vpb_timer_stop(p->ring_timer);
-				f.frametype = AST_FRAME_NULL;
-			}
-
-		} else {
-				f.frametype = AST_FRAME_NULL; /* Ignore. */
-		}
-		break;
-
-	case VPB_DTMF_DOWN:
-	case VPB_DTMF:
-		if (use_ast_dtmfdet) {
-			f.frametype = AST_FRAME_NULL;
-		} else if (ast_channel_state(p->owner) == AST_STATE_UP) {
-			f.frametype = AST_FRAME_DTMF;
-			f.subclass.integer = e->data;
-		} else
-			f.frametype = AST_FRAME_NULL;
-		break;
-
-	case VPB_TONEDETECT:
-		if (e->data == VPB_BUSY || e->data == VPB_BUSY_308 || e->data == VPB_BUSY_AUST ) {
-			ast_debug(4, "%s: handle_owned: got event: BUSY\n", p->dev);
-			if (ast_channel_state(p->owner) == AST_STATE_UP) {
-				f.subclass.integer = AST_CONTROL_HANGUP;
-			} else {
-				f.subclass.integer = AST_CONTROL_BUSY;
-			}
-		} else if (e->data == VPB_FAX) {
-			if (!p->faxhandled) {
-				if (strcmp(ast_channel_exten(p->owner), "fax")) {
-					const char *target_context = S_OR(ast_channel_macrocontext(p->owner), ast_channel_context(p->owner));
-
-					if (ast_exists_extension(p->owner, target_context, "fax", 1,
-						S_COR(ast_channel_caller(p->owner)->id.number.valid, ast_channel_caller(p->owner)->id.number.str, NULL))) {
-						ast_verb(3, "Redirecting %s to fax extension\n", ast_channel_name(p->owner));
-						/* Save the DID/DNIS when we transfer the fax call to a "fax" extension */
-						pbx_builtin_setvar_helper(p->owner, "FAXEXTEN", ast_channel_exten(p->owner));
-						if (ast_async_goto(p->owner, target_context, "fax", 1)) {
-							ast_log(LOG_WARNING, "Failed to async goto '%s' into fax of '%s'\n", ast_channel_name(p->owner), target_context);
-						}
-					} else {
-						ast_log(LOG_NOTICE, "Fax detected, but no fax extension\n");
-					}
-				} else {
-					ast_debug(1, "Already in a fax extension, not redirecting\n");
-				}
-			} else {
-				ast_debug(1, "Fax already handled\n");
-			}
-		} else if (e->data == VPB_GRUNT) {
-			if (ast_tvdiff_ms(ast_tvnow(), p->lastgrunt) > gruntdetect_timeout) {
-				/* Nothing heard on line for a very long time
-				 * Timeout connection */
-				ast_verb(3, "grunt timeout\n");
-				ast_log(LOG_NOTICE, "%s: Line hangup due of lack of conversation\n", p->dev);
-				f.subclass.integer = AST_CONTROL_HANGUP;
-			} else {
-				p->lastgrunt = ast_tvnow();
-				f.frametype = AST_FRAME_NULL;
-			}
-		} else {
-			f.frametype = AST_FRAME_NULL;
-		}
-		break;
-
-	case VPB_CALLEND:
-		#ifdef DIAL_WITH_CALL_PROGRESS
-		if (e->data == VPB_CALL_CONNECTED) {
-			f.subclass.integer = AST_CONTROL_ANSWER;
-		} else if (e->data == VPB_CALL_NO_DIAL_TONE || e->data == VPB_CALL_NO_RING_BACK) {
-			f.subclass.integer =  AST_CONTROL_CONGESTION;
-		} else if (e->data == VPB_CALL_NO_ANSWER || e->data == VPB_CALL_BUSY) {
-			f.subclass.integer = AST_CONTROL_BUSY;
-		} else if (e->data  == VPB_CALL_DISCONNECTED) {
-			f.subclass.integer = AST_CONTROL_HANGUP;
-		}
-		#else
-		ast_log(LOG_NOTICE, "%s: Got call progress callback but blind dialing \n", p->dev);
-		f.frametype = AST_FRAME_NULL;
-		#endif
-		break;
-
-	case VPB_STATION_OFFHOOK:
-		f.subclass.integer = AST_CONTROL_ANSWER;
-		break;
-
-	case VPB_DROP:
-		if ((p->mode == MODE_FXO) && (UseLoopDrop)) { /* ignore loop drop on stations */
-			if (ast_channel_state(p->owner) == AST_STATE_UP) {
-				f.subclass.integer = AST_CONTROL_HANGUP;
-			} else {
-				f.frametype = AST_FRAME_NULL;
-			}
-		}
-		break;
-	case VPB_LOOP_ONHOOK:
-		if (ast_channel_state(p->owner) == AST_STATE_UP) {
-			f.subclass.integer = AST_CONTROL_HANGUP;
-		} else {
-			f.frametype = AST_FRAME_NULL;
-		}
-		break;
-	case VPB_STATION_ONHOOK:
-		f.subclass.integer = AST_CONTROL_HANGUP;
-		break;
-
-	case VPB_STATION_FLASH:
-		f.subclass.integer = AST_CONTROL_FLASH;
-		break;
-
-	/* Called when dialing has finished and ringing starts
-	 * No indication that call has really been answered when using blind dialing
-	 */
-	case VPB_DIALEND:
-		if (p->state < 5) {
-			f.subclass.integer = AST_CONTROL_ANSWER;
-			ast_verb(2, "%s: Dialend\n", p->dev);
-		} else {
-			f.frametype = AST_FRAME_NULL;
-		}
-		break;
-
-/*	case VPB_PLAY_UNDERFLOW:
-		f.frametype = AST_FRAME_NULL;
-		vpb_reset_play_fifo_alarm(p->handle);
-		break;
-
-	case VPB_RECORD_OVERFLOW:
-		f.frametype = AST_FRAME_NULL;
-		vpb_reset_record_fifo_alarm(p->handle);
-		break;
-*/
-	default:
-		f.frametype = AST_FRAME_NULL;
-		break;
-	}
-
-/*
-	ast_verb(4, "%s: LOCKING in handle_owned [%d]\n", p->dev,res);
-	res = ast_mutex_lock(&p->lock);
-	ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
-*/
-	if (p->bridge) { /* Check what happened, see if we need to report it. */
-		switch (f.frametype) {
-		case AST_FRAME_DTMF:
-			if (	!(p->bridge->c0 == p->owner &&
-					(p->bridge->flags & AST_BRIDGE_DTMF_CHANNEL_0) ) &&
-					!(p->bridge->c1 == p->owner &&
-					(p->bridge->flags & AST_BRIDGE_DTMF_CHANNEL_1) )) {
-				/* Kill bridge, this is interesting. */
-				endbridge = 1;
-			}
-			break;
-
-		case AST_FRAME_CONTROL:
-			endbridge = 1;
-			break;
-
-		default:
-			break;
-		}
-
-		if (endbridge) {
-			if (p->bridge->fo) {
-				*p->bridge->fo = ast_frisolate(&f);
-			}
-
-			if (p->bridge->rc) {
-				*p->bridge->rc = p->owner;
-			}
-
-			ast_mutex_lock(&p->bridge->lock);
-			p->bridge->endbridge = 1;
-			ast_cond_signal(&p->bridge->cond);
-			ast_mutex_unlock(&p->bridge->lock);
-		}
-	}
-
-	if (endbridge) {
-		ast_mutex_unlock(&p->lock);
-/*
-		ast_verb(4, "%s: unLOCKING in handle_owned [%d]\n", p->dev,res);
-*/
-		return 0;
-	}
-
-	ast_verb(4, "%s: handle_owned: Prepared frame type[%d]subclass[%d], bridge=%p owner=[%s]\n",
-			p->dev, f.frametype, f.subclass.integer, (void *)p->bridge, ast_channel_name(p->owner));
-
-	/* Trylock used here to avoid deadlock that can occur if we
-	 * happen to be in here handling an event when hangup is called
-	 * Problem is that hangup holds p->owner->lock
-	 */
-	if ((f.frametype >= 0) && (f.frametype != AST_FRAME_NULL) && (p->owner)) {
-		if (ast_channel_trylock(p->owner) == 0) {
-			ast_queue_frame(p->owner, &f);
-			ast_channel_unlock(p->owner);
-			ast_verb(4, "%s: handled_owned: Queued Frame to [%s]\n", p->dev, ast_channel_name(p->owner));
-		} else {
-			ast_verbose("%s: handled_owned: Missed event %d/%d \n",
-				p->dev, f.frametype, f.subclass.integer);
-		}
-	}
-	ast_mutex_unlock(&p->lock);
-/*
-	ast_verb(4, "%s: unLOCKING in handle_owned [%d]\n", p->dev,res);
-*/
-
-	return 0;
-}
-
-static inline int monitor_handle_notowned(struct vpb_pvt *p, VPB_EVENT *e)
-{
-	char s[2] = {0};
-	struct ast_channel *owner = p->owner;
-	char cid_num[256];
-	char cid_name[256];
-/*
-	struct ast_channel *c;
-*/
-
-	char str[VPB_MAX_STR];
-
-	vpb_translate_event(e, str);
-	ast_verb(4, "%s: handle_notowned: mode=%d, event[%d][%s]=[%d]\n", p->dev, p->mode, e->type,str, e->data);
-
-	switch (e->type) {
-	case VPB_LOOP_ONHOOK:
-	case VPB_LOOP_POLARITY:
-		if (UsePolarityCID == 1) {
-			ast_verb(4, "Polarity reversal\n");
-			if (p->callerid_type == 1) {
-				ast_verb(4, "Using VPB Caller ID\n");
-				get_callerid(p);        /* UK CID before 1st ring*/
-			}
-/*			get_callerid_ast(p); */   /* Caller ID using the ast functions */
-		}
-		break;
-	case VPB_RING:
-		if (p->mode == MODE_FXO) /* FXO port ring, start * */ {
-			vpb_new(p, AST_STATE_RING, p->context, NULL, NULL);
-			if (UsePolarityCID != 1) {
-				if (p->callerid_type == 1) {
-					ast_verb(4, "Using VPB Caller ID\n");
-					get_callerid(p);        /* Australian CID only between 1st and 2nd ring  */
-				}
-				get_callerid_ast(p);    /* Caller ID using the ast functions */
-			} else {
-				ast_log(LOG_ERROR, "Setting caller ID: %s %s\n", p->cid_num, p->cid_name);
-				ast_set_callerid(p->owner, p->cid_num, p->cid_name, p->cid_num);
-				p->cid_num[0] = 0;
-				p->cid_name[0] = 0;
-			}
-
-			vpb_timer_stop(p->ring_timer);
-			vpb_timer_start(p->ring_timer);
-		}
-		break;
-
-	case VPB_RING_OFF:
-		break;
-
-	case VPB_STATION_OFFHOOK:
-		if (p->mode == MODE_IMMEDIATE) {
-			vpb_new(p,AST_STATE_RING, p->context, NULL, NULL);
-		} else {
-			ast_verb(4, "%s: handle_notowned: playing dialtone\n", p->dev);
-			playtone(p->handle, &Dialtone);
-			p->state = VPB_STATE_PLAYDIAL;
-			p->wantdtmf = 1;
-			p->ext[0] = 0;	/* Just to be sure & paranoid.*/
-		}
-		break;
-
-	case VPB_DIALEND:
-		if (p->mode == MODE_DIALTONE) {
-			if (p->state == VPB_STATE_PLAYDIAL) {
-				playtone(p->handle, &Dialtone);
-				p->wantdtmf = 1;
-				p->ext[0] = 0;	/* Just to be sure & paranoid. */
-			}
-#if 0
-			/* These are not needed as they have timers to restart them */
-			else if (p->state == VPB_STATE_PLAYBUSY) {
-				playtone(p->handle, &Busytone);
-				p->wantdtmf = 1;
-				p->ext[0] = 0;
-			} else if (p->state == VPB_STATE_PLAYRING) {
-				playtone(p->handle, &Ringbacktone);
-				p->wantdtmf = 1;
-				p->ext[0] = 0;
-			}
-#endif
-		} else {
-			ast_verb(4, "%s: handle_notowned: Got a DIALEND when not really expected\n",p->dev);
-		}
-		break;
-
-	case VPB_STATION_ONHOOK:	/* clear ext */
-		stoptone(p->handle);
-		p->wantdtmf = 1 ;
-		p->ext[0] = 0;
-		p->state = VPB_STATE_ONHOOK;
-		break;
-	case VPB_TIMEREXP:
-		if (e->data == p->dtmfidd_timer_id) {
-			if (ast_exists_extension(NULL, p->context, p->ext, 1, p->callerid)){
-				ast_verb(4, "%s: handle_notowned: DTMF IDD timer out, matching on [%s] in [%s]\n", p->dev, p->ext, p->context);
-
-				vpb_new(p, AST_STATE_RING, p->context, NULL, NULL);
-			}
-		} else if (e->data == p->ring_timer_id) {
-			/* We didnt get another ring in time! */
-			if (p->owner) {
-				if (ast_channel_state(p->owner) != AST_STATE_UP) {
-					 /* Assume caller has hung up */
-					vpb_timer_stop(p->ring_timer);
-				}
-			} else {
-				 /* No owner any more, Assume caller has hung up */
-				vpb_timer_stop(p->ring_timer);
-			}
-		}
-		break;
-
-	case VPB_DTMF:
-		if (p->state == VPB_STATE_ONHOOK){
-			/* DTMF's being passed while on-hook maybe Caller ID */
-			if (p->mode == MODE_FXO) {
-				if (e->data == DTMF_CID_START) { /* CallerID Start signal */
-					p->dtmf_caller_pos = 0; /* Leaves the first digit out */
-					memset(p->callerid, 0, sizeof(p->callerid));
-				} else if (e->data == DTMF_CID_STOP) { /* CallerID End signal */
-					p->callerid[p->dtmf_caller_pos] = '\0';
-					ast_verb(3, " %s: DTMF CallerID %s\n", p->dev, p->callerid);
-					if (owner) {
-						/*
-						if (owner->cid.cid_num)
-							ast_free(owner->cid.cid_num);
-						owner->cid.cid_num=NULL;
-						if (owner->cid.cid_name)
-							ast_free(owner->cid.cid_name);
-						owner->cid.cid_name=NULL;
-						owner->cid.cid_num = strdup(p->callerid);
-						*/
-						cid_name[0] = '\0';
-						cid_num[0] = '\0';
-						ast_callerid_split(p->callerid, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num));
-						ast_set_callerid(owner, cid_num, cid_name, cid_num);
-
-					} else {
-						ast_verb(3, " %s: DTMF CallerID: no owner to assign CID \n", p->dev);
-					}
-				} else if (p->dtmf_caller_pos < AST_MAX_EXTENSION) {
-					if (p->dtmf_caller_pos >= 0) {
-						p->callerid[p->dtmf_caller_pos] = e->data;
-					}
-					p->dtmf_caller_pos++;
-				}
-			}
-			break;
-		}
-		if (p->wantdtmf == 1) {
-			stoptone(p->handle);
-			p->wantdtmf = 0;
-		}
-		p->state = VPB_STATE_GETDTMF;
-		s[0] = e->data;
-		strncat(p->ext, s, sizeof(p->ext) - strlen(p->ext) - 1);
-		#if 0
-		if (!strcmp(p->ext, ast_pickup_ext())) {
-			/* Call pickup has been dialled! */
-			if (ast_pickup_call(c)) {
-				/* Call pickup wasnt possible */
-			}
-		} else
-		#endif
-		if (ast_exists_extension(NULL, p->context, p->ext, 1, p->callerid)) {
-			if (ast_canmatch_extension(NULL, p->context, p->ext, 1, p->callerid)) {
-				ast_verb(4, "%s: handle_notowned: Multiple matches on [%s] in [%s]\n", p->dev, p->ext, p->context);
-				/* Start DTMF IDD timer */
-				vpb_timer_stop(p->dtmfidd_timer);
-				vpb_timer_start(p->dtmfidd_timer);
-			} else {
-				ast_verb(4, "%s: handle_notowned: Matched on [%s] in [%s]\n", p->dev, p->ext , p->context);
-				vpb_new(p, AST_STATE_UP, p->context, NULL, NULL);
-			}
-		} else if (!ast_canmatch_extension(NULL, p->context, p->ext, 1, p->callerid)) {
-			if (ast_exists_extension(NULL, "default", p->ext, 1, p->callerid)) {
-				vpb_new(p, AST_STATE_UP, "default", NULL, NULL);
-			} else if (!ast_canmatch_extension(NULL, "default", p->ext, 1, p->callerid)) {
-				ast_verb(4, "%s: handle_notowned: can't match anything in %s or default\n", p->dev, p->context);
-				playtone(p->handle, &Busytone);
-				vpb_timer_stop(p->busy_timer);
-				vpb_timer_start(p->busy_timer);
-				p->state = VPB_STATE_PLAYBUSY;
-			}
-		}
-		break;
-
-	default:
-		/* Ignore.*/
-		break;
-	}
-
-	ast_verb(4, "%s: handle_notowned: mode=%d, [%d=>%d]\n", p->dev, p->mode, e->type, e->data);
-
-	return 0;
-}
-
-static void *do_monitor(void *unused)
-{
-
-	/* Monitor thread, doesn't die until explicitly killed. */
-
-	ast_verb(2, "Starting vpb monitor thread[%ld]\n", pthread_self());
-
-	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
-
-	for (;;) {
-		VPB_EVENT e;
-		VPB_EVENT je;
-		char str[VPB_MAX_STR];
-		struct vpb_pvt *p;
-
-		/*
-		ast_verb(4, "Monitor waiting for event\n");
-		*/
-
-		int res = vpb_get_event_sync(&e, VPB_WAIT_TIMEOUT);
-		if ((res == VPB_NO_EVENTS) || (res == VPB_TIME_OUT)) {
-			/*
-			if (res == VPB_NO_EVENTS) {
-				ast_verb(4, "No events....\n");
-			} else {
-				ast_verb(4, "No events, timed out....\n");
-			}
-			*/
-			continue;
-		}
-
-		if (res != VPB_OK) {
-			ast_log(LOG_ERROR,"Monitor get event error %d\n", res );
-			ast_verbose("Monitor get event error %d\n", res );
-			continue;
-		}
-
-		str[0] = 0;
-
-		p = NULL;
-
-		ast_mutex_lock(&monlock);
-		if (e.type == VPB_NULL_EVENT) {
-			ast_verb(4, "Monitor got null event\n");
-		} else {
-			vpb_translate_event(&e, str);
-			if (*str && *(str + 1)) {
-				str[strlen(str) - 1] = '\0';
-			}
-
-			ast_mutex_lock(&iflock);
-			for (p = iflist; p && p->handle != e.handle; p = p->next);
-			ast_mutex_unlock(&iflock);
-
-			if (p) {
-				ast_verb(4, "%s: Event [%d=>%s]\n",
-					p ? p->dev : "null", e.type, str);
-			}
-		}
-
-		ast_mutex_unlock(&monlock);
-
-		if (!p) {
-			if (e.type != VPB_NULL_EVENT) {
-				ast_log(LOG_WARNING, "Got event [%s][%d], no matching iface!\n", str, e.type);
-				ast_verb(4, "vpb/ERR: No interface for Event [%d=>%s] \n", e.type, str);
-			}
-			continue;
-		}
-
-		/* flush the event from the channel event Q */
-		vpb_get_event_ch_async(e.handle, &je);
-		vpb_translate_event(&je, str);
-		ast_verb(5, "%s: Flushing event [%d]=>%s\n", p->dev, je.type, str);
-
-		/* Check for ownership and locks */
-		if ((p->owner) && (!p->golock)) {
-			/* Need to get owner lock */
-			/* Safely grab both p->lock and p->owner->lock so that there
-			cannot be a race with something from the other side */
-			/*
-			ast_mutex_lock(&p->lock);
-			while (ast_mutex_trylock(&p->owner->lock)) {
-				ast_mutex_unlock(&p->lock);
-				usleep(1);
-				ast_mutex_lock(&p->lock);
-				if (!p->owner)
-					break;
-			}
-			if (p->owner)
-				p->golock = 1;
-			*/
-		}
-		/* Two scenarios: Are you owned or not. */
-		if (p->owner) {
-			monitor_handle_owned(p, &e);
-		} else {
-			monitor_handle_notowned(p, &e);
-		}
-		/* if ((!p->owner)&&(p->golock)) {
-			ast_mutex_unlock(&p->owner->lock);
-			ast_mutex_unlock(&p->lock);
-		}
-		*/
-
-	}
-
-	return NULL;
-}
-
-static int restart_monitor(void)
-{
-	int error = 0;
-
-	/* If we're supposed to be stopped -- stay stopped */
-	if (mthreadactive == -2)
-		return 0;
-
-	ast_verb(4, "Restarting monitor\n");
-
-	ast_mutex_lock(&monlock);
-	if (monitor_thread == pthread_self()) {
-		ast_log(LOG_WARNING, "Cannot kill myself\n");
-		error = -1;
-		ast_verb(4, "Monitor trying to kill monitor\n");
-	} else {
-		if (mthreadactive != -1) {
-			/* Why do other drivers kill the thread? No need says I, simply awake thread with event. */
-			VPB_EVENT e;
-			e.handle = 0;
-			e.type = VPB_EVT_NONE;
-			e.data = 0;
-
-			ast_verb(4, "Trying to reawake monitor\n");
-
-			vpb_put_event(&e);
-		} else {
-			/* Start a new monitor */
-			int pid = ast_pthread_create(&monitor_thread, NULL, do_monitor, NULL);
-			ast_verb(4, "Created new monitor thread %d\n", pid);
-			if (pid < 0) {
-				ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
-				error = -1;
-			} else {
-				mthreadactive = 0; /* Started the thread!*/
-			}
-		}
-	}
-	ast_mutex_unlock(&monlock);
-
-	ast_verb(4, "Monitor restarted\n");
-
-	return error;
-}
-
-/* Per board config that must be called after vpb_open() */
-static void mkbrd(vpb_model_t model, int echo_cancel)
-{
-	if (!bridges) {
-		if (model == vpb_model_v4pci) {
-			max_bridges = MAX_BRIDGES_V4PCI;
-		}
-		bridges = (vpb_bridge_t *)ast_calloc(1, max_bridges * sizeof(vpb_bridge_t));
-		if (!bridges) {
-			ast_log(LOG_ERROR, "Failed to initialize bridges\n");
-		} else {
-			int i;
-			for (i = 0; i < max_bridges; i++) {
-				ast_mutex_init(&bridges[i].lock);
-				ast_cond_init(&bridges[i].cond, NULL);
-			}
-		}
-	}
-	if (!echo_cancel) {
-		if (model == vpb_model_v4pci) {
-			vpb_echo_canc_disable();
-			ast_log(LOG_NOTICE, "Voicetronix echo cancellation OFF\n");
-		} else {
-			/* need to do it port by port for OpenSwitch */
-		}
-	} else {
-		if (model == vpb_model_v4pci) {
-			vpb_echo_canc_enable();
-			ast_log(LOG_NOTICE, "Voicetronix echo cancellation ON\n");
-			if (ec_supp_threshold > -1) {
-				vpb_echo_canc_set_sup_thresh(0, &ec_supp_threshold);
-				ast_log(LOG_NOTICE, "Voicetronix EC Sup Thres set\n");
-			}
-		} else {
-			/* need to do it port by port for OpenSwitch */
-		}
-	}
-}
-
-static struct vpb_pvt *mkif(int board, int channel, int mode, int gains, float txgain, float rxgain,
-			 float txswgain, float rxswgain, int bal1, int bal2, int bal3,
-			 char * callerid, int echo_cancel, int group, ast_group_t callgroup, ast_group_t pickupgroup )
-{
-	struct vpb_pvt *tmp;
-	char buf[64];
-
-	tmp = (vpb_pvt *)ast_calloc(1, sizeof(*tmp));
-
-	if (!tmp)
-		return NULL;
-
-	tmp->handle = vpb_open(board, channel);
-
-	if (tmp->handle < 0) {
-		ast_log(LOG_WARNING, "Unable to create channel vpb/%d-%d: %s\n",
-					board, channel, strerror(errno));
-		ast_free(tmp);
-		return NULL;
-	}
-
-	snprintf(tmp->dev, sizeof(tmp->dev), "vpb/%d-%d", board, channel);
-
-	tmp->mode = mode;
-
-	tmp->group = group;
-	tmp->callgroup = callgroup;
-	tmp->pickupgroup = pickupgroup;
-
-	/* Initialize dtmf caller ID position variable */
-	tmp->dtmf_caller_pos = 0;
-
-	ast_copy_string(tmp->language, language, sizeof(tmp->language));
-	ast_copy_string(tmp->context, context, sizeof(tmp->context));
-
-	tmp->callerid_type = 0;
-	if (callerid) {
-		if (strcasecmp(callerid, "on") == 0) {
-			tmp->callerid_type = 1;
-			ast_copy_string(tmp->callerid, "unknown", sizeof(tmp->callerid));
-		} else if (strcasecmp(callerid, "v23") == 0) {
-			tmp->callerid_type = 2;
-			ast_copy_string(tmp->callerid, "unknown", sizeof(tmp->callerid));
-		} else if (strcasecmp(callerid, "bell") == 0) {
-			tmp->callerid_type = 3;
-			ast_copy_string(tmp->callerid, "unknown", sizeof(tmp->callerid));
-		} else {
-			ast_copy_string(tmp->callerid, callerid, sizeof(tmp->callerid));
-		}
-	} else {
-		ast_copy_string(tmp->callerid, "unknown", sizeof(tmp->callerid));
-	}
-
-	/* check if codec balances have been set in the config file */
-	if (bal3 >= 0) {
-		if ((bal1>=0) && !(bal1 & 32)) bal1 |= 32;
-			vpb_set_codec_reg(tmp->handle, 0x42, bal3);
-	}
-	if (bal1 >= 0) {
-		vpb_set_codec_reg(tmp->handle, 0x32, bal1);
-	}
-	if (bal2 >= 0) {
-		vpb_set_codec_reg(tmp->handle, 0x3a, bal2);
-	}
-
-	if (gains & VPB_GOT_TXHWG) {
-		if (txgain > MAX_VPB_GAIN) {
-			tmp->txgain = MAX_VPB_GAIN;
-		} else if (txgain < MIN_VPB_GAIN) {
-			tmp->txgain = MIN_VPB_GAIN;
-		} else {
-			tmp->txgain = txgain;
-		}
-
-		ast_log(LOG_NOTICE, "VPB setting Tx Hw gain to [%f]\n", tmp->txgain);
-		vpb_play_set_hw_gain(tmp->handle, tmp->txgain);
-	}
-
-	if (gains & VPB_GOT_RXHWG) {
-		if (rxgain > MAX_VPB_GAIN) {
-			tmp->rxgain = MAX_VPB_GAIN;
-		} else if (rxgain < MIN_VPB_GAIN) {
-			tmp->rxgain = MIN_VPB_GAIN;
-		} else {
-			tmp->rxgain = rxgain;
-		}
-		ast_log(LOG_NOTICE, "VPB setting Rx Hw gain to [%f]\n", tmp->rxgain);
-		vpb_record_set_hw_gain(tmp->handle, tmp->rxgain);
-	}
-
-	if (gains & VPB_GOT_TXSWG) {
-		tmp->txswgain = txswgain;
-		ast_log(LOG_NOTICE, "VPB setting Tx Sw gain to [%f]\n", tmp->txswgain);
-		vpb_play_set_gain(tmp->handle, tmp->txswgain);
-	}
-
-	if (gains & VPB_GOT_RXSWG) {
-		tmp->rxswgain = rxswgain;
-		ast_log(LOG_NOTICE, "VPB setting Rx Sw gain to [%f]\n", tmp->rxswgain);
-		vpb_record_set_gain(tmp->handle, tmp->rxswgain);
-	}
-
-	tmp->vpb_model = vpb_model_unknown;
-	if (vpb_get_model(tmp->handle, buf) == VPB_OK) {
-		if (strcmp(buf, "V12PCI") == 0) {
-			tmp->vpb_model = vpb_model_v12pci;
-		} else if (strcmp(buf, "VPB4") == 0) {
-			tmp->vpb_model = vpb_model_v4pci;
-		}
-	}
-
-	ast_mutex_init(&tmp->owner_lock);
-	ast_mutex_init(&tmp->lock);
-	ast_mutex_init(&tmp->record_lock);
-	ast_mutex_init(&tmp->play_lock);
-	ast_mutex_init(&tmp->play_dtmf_lock);
-
-	/* set default read state */
-	tmp->read_state = 0;
-
-	tmp->golock = 0;
-
-	tmp->busy_timer_id = vpb_timer_get_unique_timer_id();
-	vpb_timer_open(&tmp->busy_timer, tmp->handle, tmp->busy_timer_id, TIMER_PERIOD_BUSY);
-
-	tmp->ringback_timer_id = vpb_timer_get_unique_timer_id();
-	vpb_timer_open(&tmp->ringback_timer, tmp->handle, tmp->ringback_timer_id, TIMER_PERIOD_RINGBACK);
-
-	tmp->ring_timer_id = vpb_timer_get_unique_timer_id();
-	vpb_timer_open(&tmp->ring_timer, tmp->handle, tmp->ring_timer_id, timer_period_ring);
-
-	tmp->dtmfidd_timer_id = vpb_timer_get_unique_timer_id();
-	vpb_timer_open(&tmp->dtmfidd_timer, tmp->handle, tmp->dtmfidd_timer_id, dtmf_idd);
-
-	if (mode == MODE_FXO){
-		if (use_ast_dtmfdet)
-			vpb_set_event_mask(tmp->handle, VPB_EVENTS_NODTMF);
-		else
-			vpb_set_event_mask(tmp->handle, VPB_EVENTS_ALL);
-	} else {
-/*
-		if (use_ast_dtmfdet)
-			vpb_set_event_mask(tmp->handle, VPB_EVENTS_NODTMF);
-		else
-*/
-			vpb_set_event_mask(tmp->handle, VPB_EVENTS_STAT);
-	}
-
-	if ((tmp->vpb_model == vpb_model_v12pci) && (echo_cancel)) {
-		vpb_hostecho_on(tmp->handle);
-	}
-	if (use_ast_dtmfdet) {
-		tmp->vad = ast_dsp_new();
-		ast_dsp_set_features(tmp->vad, DSP_FEATURE_DIGIT_DETECT);
-		ast_dsp_set_digitmode(tmp->vad, DSP_DIGITMODE_DTMF);
-		if (relaxdtmf)
-			ast_dsp_set_digitmode(tmp->vad, DSP_DIGITMODE_DTMF|DSP_DIGITMODE_RELAXDTMF);
-	} else {
-		tmp->vad = NULL;
-	}
-
-	/* define grunt tone */
-	vpb_settonedet(tmp->handle,&toned_ungrunt);
-
-	ast_log(LOG_NOTICE,"Voicetronix %s channel %s initialized (rxsg=%f/txsg=%f/rxhg=%f/txhg=%f)(0x%x/0x%x/0x%x)\n",
-		(tmp->vpb_model == vpb_model_v4pci) ? "V4PCI" :
-		(tmp->vpb_model == vpb_model_v12pci) ? "V12PCI" : "[Unknown model]",
-		tmp->dev, tmp->rxswgain, tmp->txswgain, tmp->rxgain, tmp->txgain, bal1, bal2, bal3);
-
-	return tmp;
-}
-
-static int vpb_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
-{
-	struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast);
-	int res = 0;
-
-	if (use_ast_ind == 1) {
-		ast_verb(4, "%s: vpb_indicate called when using Ast Indications !?!\n", p->dev);
-		return 0;
-	}
-
-	ast_verb(4, "%s: vpb_indicate [%d] state[%d]\n", p->dev, condition,ast_channel_state(ast));
-/*
-	if (ast->_state != AST_STATE_UP) {
-		ast_verb(4, "%s: vpb_indicate Not in AST_STATE_UP\n", p->dev, condition,ast->_state);
-		return res;
-	}
-*/
-
-/*
-	ast_verb(4, "%s: LOCKING in indicate \n", p->dev);
-	ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count, p->lock.__m_owner);
-*/
-	ast_mutex_lock(&p->lock);
-	switch (condition) {
-	case AST_CONTROL_BUSY:
-	case AST_CONTROL_CONGESTION:
-		if (ast_channel_state(ast) == AST_STATE_UP) {
-			playtone(p->handle, &Busytone);
-			p->state = VPB_STATE_PLAYBUSY;
-			vpb_timer_stop(p->busy_timer);
-			vpb_timer_start(p->busy_timer);
-		}
-		break;
-	case AST_CONTROL_RINGING:
-		if (ast_channel_state(ast) == AST_STATE_UP) {
-			playtone(p->handle, &Ringbacktone);
-			p->state = VPB_STATE_PLAYRING;
-			ast_verb(4, "%s: vpb indicate: setting ringback timer [%d]\n", p->dev,p->ringback_timer_id);
-
-			vpb_timer_stop(p->ringback_timer);
-			vpb_timer_start(p->ringback_timer);
-		}
-		break;
-	case AST_CONTROL_ANSWER:
-	case -1: /* -1 means stop playing? */
-		vpb_timer_stop(p->ringback_timer);
-		vpb_timer_stop(p->busy_timer);
-		stoptone(p->handle);
-		break;
-	case AST_CONTROL_HANGUP:
-		if (ast_channel_state(ast) == AST_STATE_UP) {
-			playtone(p->handle, &Busytone);
-			p->state = VPB_STATE_PLAYBUSY;
-			vpb_timer_stop(p->busy_timer);
-			vpb_timer_start(p->busy_timer);
-		}
-		break;
-	case AST_CONTROL_HOLD:
-		ast_moh_start(ast, (const char *) data, NULL);
-		break;
-	case AST_CONTROL_UNHOLD:
-		ast_moh_stop(ast);
-		break;
-	case AST_CONTROL_PVT_CAUSE_CODE:
-		res = -1;
-		break;
-	default:
-		res = 0;
-		break;
-	}
-	ast_mutex_unlock(&p->lock);
-	return res;
-}
-
-static int vpb_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
-{
-	struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(newchan);
-
-/*
-	ast_verb(4, "%s: LOCKING in fixup \n", p->dev);
-	ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
-*/
-	ast_mutex_lock(&p->lock);
-	ast_debug(1, "New owner for channel %s is %s\n", p->dev, ast_channel_name(newchan));
-
-	if (p->owner == oldchan) {
-		p->owner = newchan;
-	}
-
-	if (ast_channel_state(newchan) == AST_STATE_RINGING){
-		if (use_ast_ind == 1) {
-			ast_verb(4, "%s: vpb_fixup Calling ast_indicate\n", p->dev);
-			ast_indicate(newchan, AST_CONTROL_RINGING);
-		} else {
-			ast_verb(4, "%s: vpb_fixup Calling vpb_indicate\n", p->dev);
-			vpb_indicate(newchan, AST_CONTROL_RINGING, NULL, 0);
-		}
-	}
-
-	ast_mutex_unlock(&p->lock);
-	return 0;
-}
-
-static int vpb_digit_begin(struct ast_channel *ast, char digit)
-{
-	/* XXX Modify this callback to let Asterisk control the length of DTMF */
-	return 0;
-}
-static int vpb_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
-{
-	struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast);
-	char s[2];
-
-	if (use_ast_dtmf) {
-		ast_verb(4, "%s: vpb_digit: asked to play digit[%c] but we are using asterisk dtmf play back?!\n", p->dev, digit);
-		return 0;
-	}
-
-/*
-	ast_verb(4, "%s: LOCKING in digit \n", p->dev);
-	ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
-*/
-	ast_mutex_lock(&p->lock);
-
-
-	s[0] = digit;
-	s[1] = '\0';
-
-	ast_verb(4, "%s: vpb_digit: asked to play digit[%s]\n", p->dev, s);
-
-	ast_mutex_lock(&p->play_dtmf_lock);
-	strncat(p->play_dtmf, s, sizeof(p->play_dtmf) - strlen(p->play_dtmf) - 1);
-	ast_mutex_unlock(&p->play_dtmf_lock);
-
-	ast_mutex_unlock(&p->lock);
-	return 0;
-}
-
-/* Places a call out of a VPB channel */
-static int vpb_call(struct ast_channel *ast, const char *dest, int timeout)
-{
-	struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast);
-	int res = 0, i;
-	const char *s = strrchr(dest, '/');
-	char dialstring[254] = "";
-
-/*
-	ast_verb(4, "%s: LOCKING in call \n", p->dev);
-	ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
-*/
-	ast_mutex_lock(&p->lock);
-	ast_verb(4, "%s: starting call to [%s]\n", p->dev, dest);
-
-	if (s)
-		s = s + 1;
-	else
-		s = dest;
-	ast_copy_string(dialstring, s, sizeof(dialstring));
-	for (i = 0; dialstring[i] != '\0'; i++) {
-		if ((dialstring[i] == 'w') || (dialstring[i] == 'W'))
-			dialstring[i] = ',';
-		else if ((dialstring[i] == 'f') || (dialstring[i] == 'F'))
-			dialstring[i] = '&';
-	}
-
-	if (ast_channel_state(ast) != AST_STATE_DOWN && ast_channel_state(ast) != AST_STATE_RESERVED) {
-		ast_log(LOG_WARNING, "vpb_call on %s neither down nor reserved!\n", ast_channel_name(ast));
-		ast_mutex_unlock(&p->lock);
-		return -1;
-	}
-	if (p->mode != MODE_FXO)  /* Station port, ring it. */
-		vpb_ring_station_async(p->handle, 2);
-	else {
-		VPB_CALL call;
-		int j;
-
-		/* Dial must timeout or it can leave channels unuseable */
-		if (timeout == 0) {
-			timeout = TIMER_PERIOD_NOANSWER;
-		} else {
-			timeout = timeout * 1000; /* convert from secs to ms. */
-		}
-
-		/* These timeouts are only used with call progress dialing */
-		call.dialtones = 1; /* Number of dialtones to get outside line */
-		call.dialtone_timeout = VPB_DIALTONE_WAIT; /* Wait this long for dialtone (ms) */
-		call.ringback_timeout = VPB_RINGWAIT; /* Wait this long for ringing after dialing (ms) */
-		call.inter_ringback_timeout = VPB_CONNECTED_WAIT; /* If ringing stops for this long consider it connected (ms) */
-		call.answer_timeout = timeout; /* Time to wait for answer after ringing starts (ms) */
-		memcpy(&call.tone_map,  DialToneMap, sizeof(DialToneMap));
-		vpb_set_call(p->handle, &call);
-
-		ast_verb(2, "%s: Calling %s on %s \n",p->dev, dialstring, ast_channel_name(ast));
-
-		ast_verb(2, "%s: Dial parms for %s %d/%dms/%dms/%dms/%dms\n", p->dev,
-				ast_channel_name(ast), call.dialtones, call.dialtone_timeout,
-				call.ringback_timeout, call.inter_ringback_timeout,
-				call.answer_timeout);
-		for (j = 0; !call.tone_map[j].terminate; j++) {
-			ast_verb(2, "%s: Dial parms for %s tone %d->%d\n", p->dev,
-					ast_channel_name(ast), call.tone_map[j].tone_id, call.tone_map[j].call_id);
-		}
-
-		ast_verb(4, "%s: Disabling Loop Drop detection\n", p->dev);
-		vpb_disable_event(p->handle, VPB_MDROP);
-		vpb_sethook_sync(p->handle, VPB_OFFHOOK);
-		p->state = VPB_STATE_OFFHOOK;
-
-		#ifndef DIAL_WITH_CALL_PROGRESS
-		vpb_sleep(300);
-		ast_verb(4, "%s: Enabling Loop Drop detection\n", p->dev);
-		vpb_enable_event(p->handle, VPB_MDROP);
-		res = vpb_dial_async(p->handle, dialstring);
-		#else
-		ast_verb(4, "%s: Enabling Loop Drop detection\n", p->dev);
-		vpb_enable_event(p->handle, VPB_MDROP);
-		res = vpb_call_async(p->handle, dialstring);
-		#endif
-
-		if (res != VPB_OK) {
-			ast_debug(1, "Call on %s to %s failed: %d\n", ast_channel_name(ast), s, res);
-			res = -1;
-		} else
-			res = 0;
-	}
-
-	ast_verb(3, "%s: VPB Calling %s [t=%d] on %s returned %d\n", p->dev , s, timeout, ast_channel_name(ast), res);
-	if (res == 0) {
-		ast_setstate(ast, AST_STATE_RINGING);
-		ast_queue_control(ast, AST_CONTROL_RINGING);
-	}
-
-	if (!p->readthread) {
-		ast_pthread_create(&p->readthread, NULL, do_chanreads, (void *)p);
-	}
-
-	ast_mutex_unlock(&p->lock);
-	return res;
-}
-
-static int vpb_hangup(struct ast_channel *ast)
-{
-	struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast);
-	VPB_EVENT je;
-	char str[VPB_MAX_STR];
-
-/*
-	ast_verb(4, "%s: LOCKING in hangup \n", p->dev);
-	ast_verb(4, "%s: LOCKING in hangup count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
-	ast_verb(4, "%s: LOCKING pthread_self(%d)\n", p->dev,pthread_self());
-	ast_mutex_lock(&p->lock);
-*/
-	ast_verb(2, "%s: Hangup requested\n", ast_channel_name(ast));
-
-	if (!ast_channel_tech(ast) || !ast_channel_tech_pvt(ast)) {
-		ast_log(LOG_WARNING, "%s: channel not connected?\n", ast_channel_name(ast));
-		ast_mutex_unlock(&p->lock);
-		/* Free up ast dsp if we have one */
-		if (use_ast_dtmfdet && p->vad) {
-			ast_dsp_free(p->vad);
-			p->vad = NULL;
-		}
-		return 0;
-	}
-
-
-
-	/* Stop record */
-	p->stopreads = 1;
-	if (p->readthread) {
-		pthread_join(p->readthread, NULL);
-		ast_verb(4, "%s: stopped record thread \n", ast_channel_name(ast));
-	}
-
-	/* Stop play */
-	if (p->lastoutput != -1) {
-		ast_verb(2, "%s: Ending play mode \n", ast_channel_name(ast));
-		vpb_play_terminate(p->handle);
-		ast_mutex_lock(&p->play_lock);
-		vpb_play_buf_finish(p->handle);
-		ast_mutex_unlock(&p->play_lock);
-	}
-
-	ast_verb(4, "%s: Setting state down\n", ast_channel_name(ast));
-	ast_setstate(ast, AST_STATE_DOWN);
-
-
-/*
-	ast_verb(4, "%s: LOCKING in hangup \n", p->dev);
-	ast_verb(4, "%s: LOCKING in hangup count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
-	ast_verb(4, "%s: LOCKING pthread_self(%d)\n", p->dev,pthread_self());
-*/
-	ast_mutex_lock(&p->lock);
-
-	if (p->mode != MODE_FXO) {
-		/* station port. */
-		vpb_ring_station_async(p->handle, 0);
-		if (p->state != VPB_STATE_ONHOOK) {
-			/* This is causing a "dial end" "play tone" loop
-			playtone(p->handle, &Busytone);
-			p->state = VPB_STATE_PLAYBUSY;
-			ast_verb(5, "%s: Station offhook[%d], playing busy tone\n",
-								ast->name,p->state);
-			*/
-		} else {
-			stoptone(p->handle);
-		}
-		#ifdef VPB_PRI
-		vpb_setloop_async(p->handle, VPB_OFFHOOK);
-		vpb_sleep(100);
-		vpb_setloop_async(p->handle, VPB_ONHOOK);
-		#endif
-	} else {
-		stoptone(p->handle); /* Terminates any dialing */
-		vpb_sethook_sync(p->handle, VPB_ONHOOK);
-		p->state=VPB_STATE_ONHOOK;
-	}
-	while (VPB_OK == vpb_get_event_ch_async(p->handle, &je)) {
-		vpb_translate_event(&je, str);
-		ast_verb(4, "%s: Flushing event [%d]=>%s\n", ast_channel_name(ast), je.type, str);
-	}
-
-	p->readthread = 0;
-	p->lastoutput = -1;
-	p->lastinput = -1;
-	p->last_ignore_dtmf = 1;
-	p->ext[0] = 0;
-	p->dialtone = 0;
-
-	p->owner = NULL;
-	ast_channel_tech_pvt_set(ast, NULL);
-
-	/* Free up ast dsp if we have one */
-	if (use_ast_dtmfdet && p->vad) {
-		ast_dsp_free(p->vad);
-		p->vad = NULL;
-	}
-
-	ast_verb(2, "%s: Hangup complete\n", ast_channel_name(ast));
-
-	restart_monitor();
-	ast_mutex_unlock(&p->lock);
-	return 0;
-}
-
-static int vpb_answer(struct ast_channel *ast)
-{
-	struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast);
-/*
-	VPB_EVENT je;
-	int ret;
-	ast_verb(4, "%s: LOCKING in answer \n", p->dev);
-	ast_verb(4, "%s: LOCKING count[%d] owner[%d] \n", p->dev, p->lock.__m_count,p->lock.__m_owner);
-*/
-	ast_mutex_lock(&p->lock);
-
-	ast_verb(4, "%s: Answering channel\n", p->dev);
-
-	if (p->mode == MODE_FXO) {
-		ast_verb(4, "%s: Disabling Loop Drop detection\n", p->dev);
-		vpb_disable_event(p->handle, VPB_MDROP);
-	}
-
-	if (ast_channel_state(ast) != AST_STATE_UP) {
-		if (p->mode == MODE_FXO) {
-			vpb_sethook_sync(p->handle, VPB_OFFHOOK);
-			p->state = VPB_STATE_OFFHOOK;
-/*			vpb_sleep(500);
-			ret = vpb_get_event_ch_async(p->handle, &je);
-			if ((ret == VPB_OK) && ((je.type != VPB_DROP)&&(je.type != VPB_RING))){
-				ast_verb(4, "%s: Answer collected a wrong event!!\n", p->dev);
-				vpb_put_event(&je);
-			}
-*/
-		}
-		ast_setstate(ast, AST_STATE_UP);
-
-		ast_verb(2, "%s: Answered call on %s [%s]\n", p->dev,
-					 ast_channel_name(ast), (p->mode == MODE_FXO) ? "FXO" : "FXS");
-
-		ast_channel_rings_set(ast, 0);
-		if (!p->readthread) {
-	/*		res = ast_mutex_unlock(&p->lock); */
-	/*		ast_verbose("%s: unLOCKING in answer [%d]\n", p->dev,res); */
-			ast_pthread_create(&p->readthread, NULL, do_chanreads, (void *)p);
-		} else {
-			ast_verb(4, "%s: Record thread already running!!\n", p->dev);
-		}
-	} else {
-		ast_verb(4, "%s: Answered state is up\n", p->dev);
-	/*	res = ast_mutex_unlock(&p->lock); */
-	/*	ast_verbose("%s: unLOCKING in answer [%d]\n", p->dev,res); */
-	}
-	vpb_sleep(500);
-	if (p->mode == MODE_FXO) {
-		ast_verb(4, "%s: Re-enabling Loop Drop detection\n", p->dev);
-		vpb_enable_event(p->handle, VPB_MDROP);
-	}
-	ast_mutex_unlock(&p->lock);
-	return 0;
-}
-
-static struct ast_frame *vpb_read(struct ast_channel *ast)
-{
-	struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast);
-	static struct ast_frame f = { AST_FRAME_NULL };
-
-	f.src = "vpb";
-	ast_log(LOG_NOTICE, "%s: vpb_read: should never be called!\n", p->dev);
-	ast_verbose("%s: vpb_read: should never be called!\n", p->dev);
-
-	return &f;
-}
-
-static inline AudioCompress ast2vpbformat(struct ast_format *format)
-{
-	if (ast_format_cmp(format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) {
-		return VPB_ALAW;
-	} else if (ast_format_cmp(format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) {
-		return VPB_LINEAR;
-	} else if (ast_format_cmp(format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) {
-		return VPB_MULAW;
-	} else if (ast_format_cmp(format, ast_format_adpcm) == AST_FORMAT_CMP_EQUAL) {
-		return VPB_OKIADPCM;
-	} else {
-		return VPB_RAW;
-	}
-}
-
-static inline const char * ast2vpbformatname(struct ast_format *format)
-{
-	if (ast_format_cmp(format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) {
-		return "AST_FORMAT_ALAW:VPB_ALAW";
-	} else if (ast_format_cmp(format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) {
-		return "AST_FORMAT_SLINEAR:VPB_LINEAR";
-	} else if (ast_format_cmp(format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) {
-		return "AST_FORMAT_ULAW:VPB_MULAW";
-	} else if (ast_format_cmp(format, ast_format_adpcm) == AST_FORMAT_CMP_EQUAL) {
-		return "AST_FORMAT_ADPCM:VPB_OKIADPCM";
-	} else {
-		return "UNKN:UNKN";
-	}
-}
-
-static inline int astformatbits(struct ast_format *format)
-{
-	if (ast_format_cmp(format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) {
-		return 16;
-	} else if (ast_format_cmp(format, ast_format_adpcm) == AST_FORMAT_CMP_EQUAL) {
-		return 4;
-	} else {
-		return 8;
-	}
-}
-
-int a_gain_vector(float g, short *v, int n)
-{
-	int i;
-	float tmp;
-	for (i = 0; i < n; i++) {
-		tmp = g * v[i];
-		if (tmp > 32767.0)
-			tmp = 32767.0;
-		if (tmp < -32768.0)
-			tmp = -32768.0;
-		v[i] = (short)tmp;
-	}
-	return i;
-}
-
-/* Writes a frame of voice data to a VPB channel */
-static int vpb_write(struct ast_channel *ast, struct ast_frame *frame)
-{
-	struct vpb_pvt *p = (struct vpb_pvt *)ast_channel_tech_pvt(ast);
-	int res = 0;
-	AudioCompress fmt = VPB_RAW;
-	struct timeval play_buf_time_start;
-	int tdiff;
-
-/*	ast_mutex_lock(&p->lock); */
-	ast_verb(6, "%s: vpb_write: Writing to channel\n", p->dev);
-
-	if (frame->frametype != AST_FRAME_VOICE) {
-		ast_verb(4, "%s: vpb_write: Don't know how to handle from type %d\n", ast_channel_name(ast), frame->frametype);
-/*		ast_mutex_unlock(&p->lock); */
-		return 0;
-	} else if (ast_channel_state(ast) != AST_STATE_UP) {
-		ast_verb(4, "%s: vpb_write: Attempt to Write frame type[%d]subclass[%s] on not up chan(state[%d])\n",
-			ast_channel_name(ast), frame->frametype, ast_format_get_name(frame->subclass.format), ast_channel_state(ast));
-		p->lastoutput = -1;
-/*		ast_mutex_unlock(&p->lock); */
-		return 0;
-	}
-/*	ast_debug(1, "%s: vpb_write: Checked frame type..\n", p->dev); */
-
-
-	fmt = ast2vpbformat(frame->subclass.format);
-	if (fmt < 0) {
-		ast_log(LOG_WARNING, "%s: vpb_write: Cannot handle frames of %s format!\n", ast_channel_name(ast),
-			ast_format_get_name(frame->subclass.format));
-		return -1;
-	}
-
-	tdiff = ast_tvdiff_ms(ast_tvnow(), p->lastplay);
-	ast_debug(1, "%s: vpb_write: time since last play(%d) \n", p->dev, tdiff);
-	if (tdiff < (VPB_SAMPLES / 8 - 1)) {
-		ast_debug(1, "%s: vpb_write: Asked to play too often (%d) (%d)\n", p->dev, tdiff, frame->datalen);
-/*		return 0; */
-	}
-	p->lastplay = ast_tvnow();
-/*
-	ast_debug(1, "%s: vpb_write: Checked frame format..\n", p->dev);
-*/
-
-	ast_mutex_lock(&p->play_lock);
-
-/*
-	ast_debug(1, "%s: vpb_write: Got play lock..\n", p->dev);
-*/
-
-	/* Check if we have set up the play_buf */
-	if (p->lastoutput == -1) {
-		vpb_play_buf_start(p->handle, fmt);
-		ast_verb(2, "%s: vpb_write: Starting play mode (codec=%d)[%s]\n", p->dev, fmt, ast2vpbformatname(frame->subclass.format));
-		p->lastoutput = fmt;
-		ast_mutex_unlock(&p->play_lock);
-		return 0;
-	} else if (p->lastoutput != fmt) {
-		vpb_play_buf_finish(p->handle);
-		vpb_play_buf_start(p->handle, fmt);
-		ast_verb(2, "%s: vpb_write: Changed play format (%d=>%d)\n", p->dev, p->lastoutput, fmt);
-		ast_mutex_unlock(&p->play_lock);
-		return 0;
-	}
-	p->lastoutput = fmt;
-
-
-
-	/* Apply extra gain ! */
-	if( p->txswgain > MAX_VPB_GAIN )
-		a_gain_vector(p->txswgain - MAX_VPB_GAIN , (short*)frame->data.ptr, frame->datalen / sizeof(short));
-
-/*	ast_debug(1, "%s: vpb_write: Applied gain..\n", p->dev); */
-/*	ast_debug(1, "%s: vpb_write: play_buf_time %d\n", p->dev, p->play_buf_time); */
-
-	if ((p->read_state == 1) && (p->play_buf_time < 5)){
-		play_buf_time_start = ast_tvnow();
-/*		res = vpb_play_buf_sync(p->handle, (char *)frame->data, tdiff * 8 * 2); */
-		res = vpb_play_buf_sync(p->handle, (char *)frame->data.ptr, frame->datalen);
-		if(res == VPB_OK) {
-			short * data = (short*)frame->data.ptr;
-			ast_verb(6, "%s: vpb_write: Wrote chan (codec=%d) %d %d\n", p->dev, fmt, data[0], data[1]);
-		}
-		p->play_buf_time = ast_tvdiff_ms(ast_tvnow(), play_buf_time_start);
-	} else {
-		p->chuck_count++;
-		ast_debug(1, "%s: vpb_write: Tossed data away, tooooo much data!![%d]\n", p->dev, p->chuck_count);
-		p->play_buf_time = 0;
-	}
-
-	ast_mutex_unlock(&p->play_lock);
-/*	ast_mutex_unlock(&p->lock); */
-	ast_verb(6, "%s: vpb_write: Done Writing to channel\n", p->dev);
-	return 0;
-}
-
-/* Read monitor thread function. */
-static void *do_chanreads(void *pvt)
-{
-	struct vpb_pvt *p = (struct vpb_pvt *)pvt;
-	struct ast_frame *fr = &p->fr;
-	char *readbuf = ((char *)p->buf) + AST_FRIENDLY_OFFSET;
-	int bridgerec = 0;
-	struct ast_format *tmpfmt;
-	int readlen, res, trycnt=0;
-	AudioCompress fmt;
-	int ignore_dtmf;
-	const char * getdtmf_var = NULL;
-
-	fr->frametype = AST_FRAME_VOICE;
-	fr->src = "vpb";
-	fr->mallocd = 0;
-	fr->delivery.tv_sec = 0;
-	fr->delivery.tv_usec = 0;
-	fr->samples = VPB_SAMPLES;
-	fr->offset = AST_FRIENDLY_OFFSET;
-	memset(p->buf, 0, sizeof p->buf);
-
-	ast_verb(3, "%s: chanreads: starting thread\n", p->dev);
-	ast_mutex_lock(&p->record_lock);
-
-	p->stopreads = 0;
-	p->read_state = 1;
-	while (!p->stopreads && p->owner) {
-
-		ast_verb(5, "%s: chanreads: Starting cycle ...\n", p->dev);
-		ast_verb(5, "%s: chanreads: Checking bridge \n", p->dev);
-		if (p->bridge) {
-			bridgerec = 0;
-		} else {
-			bridgerec = ast_channel_is_bridged(p->owner) ? 1 : 0;
-		}
-
-/*		if ((p->owner->_state != AST_STATE_UP) || !bridgerec) */
-		if ((ast_channel_state(p->owner) != AST_STATE_UP)) {
-			if (ast_channel_state(p->owner) != AST_STATE_UP) {
-				ast_verb(5, "%s: chanreads: Im not up[%d]\n", p->dev, ast_channel_state(p->owner));
-			} else {
-				ast_verb(5, "%s: chanreads: No bridgerec[%d]\n", p->dev, bridgerec);
-			}
-			vpb_sleep(10);
-			continue;
-		}
-
-		/* Voicetronix DTMF detection can be triggered off ordinary speech
-		 * This leads to annoying beeps during the conversation
-		 * Avoid this problem by just setting VPB_GETDTMF when you want to listen for DTMF
-		 */
-		/* ignore_dtmf = 1; */
-		ignore_dtmf = 0; /* set this to 1 to turn this feature on */
-		getdtmf_var = pbx_builtin_getvar_helper(p->owner, "VPB_GETDTMF");
-		if (getdtmf_var && strcasecmp(getdtmf_var, "yes") == 0)
-			ignore_dtmf = 0;
-
-		if ((ignore_dtmf != p->last_ignore_dtmf) && (!use_ast_dtmfdet)){
-			ast_verb(2, "%s:Now %s DTMF \n",
-					p->dev, ignore_dtmf ? "ignoring" : "listening for");
-			vpb_set_event_mask(p->handle, ignore_dtmf ? VPB_EVENTS_NODTMF : VPB_EVENTS_ALL );
-		}
-		p->last_ignore_dtmf = ignore_dtmf;
-
-		/* Play DTMF digits here to avoid problem you get if playing a digit during
-		 * a record operation
-		 */
-		ast_verb(6, "%s: chanreads: Checking dtmf's \n", p->dev);
-		ast_mutex_lock(&p->play_dtmf_lock);
-		if (p->play_dtmf[0]) {
-			/* Try to ignore DTMF event we get after playing digit */
-			/* This DTMF is played by asterisk and leads to an annoying trailing beep on CISCO phones */
-			if (!ignore_dtmf) {
-				vpb_set_event_mask(p->handle, VPB_EVENTS_NODTMF );
-			}
-			if (p->bridge == NULL) {
-				vpb_dial_sync(p->handle, p->play_dtmf);
-				ast_verb(2, "%s: chanreads: Played DTMF %s\n", p->dev, p->play_dtmf);
-			} else {
-				ast_verb(2, "%s: chanreads: Not playing DTMF frame on native bridge\n", p->dev);
-			}
-			p->play_dtmf[0] = '\0';
-			ast_mutex_unlock(&p->play_dtmf_lock);
-			vpb_sleep(700); /* Long enough to miss echo and DTMF event */
-			if( !ignore_dtmf)
-				vpb_set_event_mask(p->handle, VPB_EVENTS_ALL);
-			continue;
-		}
-		ast_mutex_unlock(&p->play_dtmf_lock);
-
-		if (p->owner) {
-			tmpfmt = ast_channel_rawreadformat(p->owner);
-		} else {
-			tmpfmt = ast_format_slin;
-		}
-		fmt = ast2vpbformat(tmpfmt);
-		if (fmt < 0) {
-			ast_log(LOG_WARNING, "%s: Record failure (unsupported format %s)\n", p->dev, ast_format_get_name(tmpfmt));
-			return NULL;
-		}
-		readlen = VPB_SAMPLES * astformatbits(tmpfmt) / 8;
-
-		if (p->lastinput == -1) {
-			vpb_record_buf_start(p->handle, fmt);
-/*			vpb_reset_record_fifo_alarm(p->handle); */
-			p->lastinput = fmt;
-			ast_verb(2, "%s: Starting record mode (codec=%d)[%s]\n", p->dev, fmt, ast2vpbformatname(tmpfmt));
-			continue;
-		} else if (p->lastinput != fmt) {
-			vpb_record_buf_finish(p->handle);
-			vpb_record_buf_start(p->handle, fmt);
-			p->lastinput = fmt;
-			ast_verb(2, "%s: Changed record format (%d=>%d)\n", p->dev, p->lastinput, fmt);
-			continue;
-		}
-
-		/* Read only if up and not bridged, or a bridge for which we can read. */
-		ast_verb(6, "%s: chanreads: getting buffer!\n", p->dev);
-		if( (res = vpb_record_buf_sync(p->handle, readbuf, readlen) ) == VPB_OK ) {
-			ast_verb(6, "%s: chanreads: got buffer!\n", p->dev);
-			/* Apply extra gain ! */
-			if( p->rxswgain > MAX_VPB_GAIN )
-				a_gain_vector(p->rxswgain - MAX_VPB_GAIN, (short *)readbuf, readlen / sizeof(short));
-			ast_verb(6, "%s: chanreads: applied gain\n", p->dev);
-
-			fr->subclass.format = tmpfmt;
-			fr->data.ptr = readbuf;
-			fr->datalen = readlen;
-			fr->frametype = AST_FRAME_VOICE;
-
-			if ((use_ast_dtmfdet) && (p->vad)) {
-				fr = ast_dsp_process(p->owner, p->vad, fr);
-				if (fr && (fr->frametype == AST_FRAME_DTMF)) {
-					ast_debug(1, "%s: chanreads: Detected DTMF '%c'\n", p->dev, fr->subclass.integer);
-				} else if (fr->subclass.integer == 'f') {
-				}
-			}
-			/* Using trylock here to prevent deadlock when channel is hungup
-			 * (ast_hangup() immediately gets lock)
-			 */
-			if (p->owner && !p->stopreads) {
-				ast_verb(6, "%s: chanreads: queueing buffer on read frame q (state[%d])\n", p->dev, ast_channel_state(p->owner));
-				do {
-					res = ast_channel_trylock(p->owner);
-					trycnt++;
-				} while ((res !=0 ) && (trycnt < 300));
-				if (res == 0) {
-					ast_queue_frame(p->owner, fr);
-					ast_channel_unlock(p->owner);
-				} else {
-					ast_verb(5, "%s: chanreads: Couldnt get lock after %d tries!\n", p->dev, trycnt);
-				}
-				trycnt = 0;
-
-/*
-				res = ast_mutex_trylock(&p->owner->lock);
-				if (res == 0)  {
-					ast_queue_frame(p->owner, fr);
-					ast_mutex_unlock(&p->owner->lock);
-				} else {
-					if (res == EINVAL)
-						ast_verb(5, "%s: chanreads: try owner->lock gave me EINVAL[%d]\n", p->dev, res);
-					else if (res == EBUSY)
-						ast_verb(5, "%s: chanreads: try owner->lock gave me EBUSY[%d]\n", p->dev, res);
-					while (res != 0) {
-					res = ast_mutex_trylock(&p->owner->lock);
-					}
-					if (res == 0) {
-						ast_queue_frame(p->owner, fr);
-						ast_mutex_unlock(&p->owner->lock);
-					} else {
-						if (res == EINVAL) {
-							ast_verb(5, "%s: chanreads: try owner->lock gave me EINVAL[%d]\n", p->dev, res);
-						} else if (res == EBUSY) {
-							ast_verb(5, "%s: chanreads: try owner->lock gave me EBUSY[%d]\n", p->dev, res);
-						}
-						ast_verb(5, "%s: chanreads: Couldnt get lock on owner[%s][%d][%d] channel to send frame!\n", p->dev, p->owner->name, (int)p->owner->lock.__m_owner, (int)p->owner->lock.__m_count);
-					}
-				}
-*/
-					short *data = (short *)readbuf;
-				ast_verb(7, "%s: Read channel (codec=%d) %d %d\n", p->dev, fmt, data[0], data[1]);
-			} else {
-				ast_verb(5, "%s: p->stopreads[%d] p->owner[%p]\n", p->dev, p->stopreads, (void *)p->owner);
-			}
-		}
-		ast_verb(5, "%s: chanreads: Finished cycle...\n", p->dev);
-	}
-	p->read_state = 0;
-
-	/* When stopreads seen, go away! */
-	vpb_record_buf_finish(p->handle);
-	p->read_state = 0;
-	ast_mutex_unlock(&p->record_lock);
-
-	ast_verb(2, "%s: Ending record mode (%d/%s)\n",
-			 p->dev, p->stopreads, p->owner ? "yes" : "no");
-	return NULL;
-}
-
-static struct ast_channel *vpb_new(struct vpb_pvt *me, enum ast_channel_state state, const char *context, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor)
-{
-	struct ast_channel *tmp;
-	char cid_num[256];
-	char cid_name[256];
-
-	if (me->owner) {
-	    ast_log(LOG_WARNING, "Called vpb_new on owned channel (%s) ?!\n", me->dev);
-	    return NULL;
-	}
-	ast_verb(4, "%s: New call for context [%s]\n", me->dev, context);
-
-	tmp = ast_channel_alloc(1, state, 0, 0, "", me->ext, me->context, assignedids, requestor, AST_AMA_NONE, "%s", me->dev);
-	if (tmp) {
-		if (use_ast_ind == 1){
-			ast_channel_tech_set(tmp, &vpb_tech_indicate);
-		} else {
-			ast_channel_tech_set(tmp, &vpb_tech);
-		}
-
-		ast_channel_callgroup_set(tmp, me->callgroup);
-		ast_channel_pickupgroup_set(tmp, me->pickupgroup);
-
-		/* Linear is the preferred format. Although Voicetronix supports other formats
-		 * they are all converted to/from linear in the vpb code. Best for us to use
-		 * linear since we can then adjust volume in this modules.
-		 */
-		ast_channel_nativeformats_set(tmp, vpb_tech.capabilities);
-		ast_channel_set_rawreadformat(tmp, ast_format_slin);
-		ast_channel_set_rawwriteformat(tmp, ast_format_slin);
-		if (state == AST_STATE_RING) {
-			ast_channel_rings_set(tmp, 1);
-			cid_name[0] = '\0';
-			cid_num[0] = '\0';
-			ast_callerid_split(me->callerid, cid_name, sizeof(cid_name), cid_num, sizeof(cid_num));
-			ast_set_callerid(tmp, cid_num, cid_name, cid_num);
-		}
-		ast_channel_tech_pvt_set(tmp, me);
-
-		ast_channel_context_set(tmp, context);
-		if (!ast_strlen_zero(me->ext))
-			ast_channel_exten_set(tmp, me->ext);
-		else
-			ast_channel_exten_set(tmp, "s");
-		if (!ast_strlen_zero(me->language))
-			ast_channel_language_set(tmp, me->language);
-		ast_channel_unlock(tmp);
-
-		me->owner = tmp;
-
-		me->bridge = NULL;
-		me->lastoutput = -1;
-		me->lastinput = -1;
-		me->last_ignore_dtmf = 1;
-		me->readthread = 0;
-		me->play_dtmf[0] = '\0';
-		me->faxhandled = 0;
-
-		me->lastgrunt = ast_tvnow(); /* Assume at least one grunt tone seen now. */
-		me->lastplay = ast_tvnow(); /* Assume at least one grunt tone seen now. */
-
-		if (state != AST_STATE_DOWN) {
-			if ((me->mode != MODE_FXO) && (state != AST_STATE_UP)) {
-				vpb_answer(tmp);
-			}
-			if (ast_pbx_start(tmp)) {
-				ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(tmp));
-				ast_hangup(tmp);
-		   	}
-		}
-	} else {
-		ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
-	}
-	return tmp;
-}
-
-static struct ast_channel *vpb_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
-{
-	struct vpb_pvt *p;
-	struct ast_channel *tmp = NULL;
-	char *sepstr, *name;
-	const char *s;
-	int group = -1;
-
-	if (!(ast_format_cap_iscompatible_format(cap, ast_format_slin))) {
-		struct ast_str *buf;
-
-		buf = ast_str_create(AST_FORMAT_CAP_NAMES_LEN);
-		if (!buf) {
-			return NULL;
-		}
-		ast_log(LOG_NOTICE, "Asked to create a channel for unsupported formats: %s\n",
-			ast_format_cap_get_names(cap, &buf));
-		ast_free(buf);
-		return NULL;
-	}
-
-	name = ast_strdup(S_OR(data, ""));
-
-	sepstr = name;
-	s = strsep(&sepstr, "/"); /* Handle / issues */
-	if (!s)
-		s = "";
-	/* Check if we are looking for a group */
-	if (toupper(name[0]) == 'G' || toupper(name[0]) == 'R') {
-		group = atoi(name + 1);
-	}
-	/* Search for an unowned channel */
-	ast_mutex_lock(&iflock);
-	for (p = iflist; p; p = p->next) {
-		if (group == -1) {
-			if (strncmp(s, p->dev + 4, sizeof p->dev) == 0) {
-				if (!p->owner) {
-					tmp = vpb_new(p, AST_STATE_DOWN, p->context, assignedids, requestor);
-					break;
-				}
-			}
-		} else {
-			if ((p->group == group) && (!p->owner)) {
-				tmp = vpb_new(p, AST_STATE_DOWN, p->context, assignedids, requestor);
-				break;
-			}
-		}
-	}
-	ast_mutex_unlock(&iflock);
-
-
-	ast_verb(2, " %s requested, got: [%s]\n", name, tmp ? ast_channel_name(tmp) : "None");
-
-	ast_free(name);
-
-	restart_monitor();
-	return tmp;
-}
-
-static float parse_gain_value(const char *gain_type, const char *value)
-{
-	float gain;
-
-	/* try to scan number */
-	if (sscanf(value, "%f", &gain) != 1) {
-		ast_log(LOG_ERROR, "Invalid %s value '%s' in '%s' config\n", value, gain_type, config);
-		return DEFAULT_GAIN;
-	}
-
-
-	/* percentage? */
-	/*if (value[strlen(value) - 1] == '%') */
-	/*	return gain / (float)100; */
-
-	return gain;
-}
-
-
-static int unload_module(void)
-{
-	struct vpb_pvt *p;
-	/* First, take us out of the channel loop */
-	if (use_ast_ind == 1){
-		ast_channel_unregister(&vpb_tech_indicate);
-	} else {
-		ast_channel_unregister(&vpb_tech);
-	}
-
-	ast_mutex_lock(&iflock);
-	/* Hangup all interfaces if they have an owner */
-	for (p = iflist; p; p = p->next) {
-		if (p->owner)
-			ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
-	}
-	iflist = NULL;
-	ast_mutex_unlock(&iflock);
-
-	ast_mutex_lock(&monlock);
-	if (mthreadactive > -1) {
-		pthread_cancel(monitor_thread);
-		pthread_join(monitor_thread, NULL);
-	}
-	mthreadactive = -2;
-	ast_mutex_unlock(&monlock);
-
-	ast_mutex_lock(&iflock);
-	/* Destroy all the interfaces and free their memory */
-
-	while (iflist) {
-		p = iflist;
-		ast_mutex_destroy(&p->lock);
-		pthread_cancel(p->readthread);
-		ast_mutex_destroy(&p->owner_lock);
-		ast_mutex_destroy(&p->record_lock);
-		ast_mutex_destroy(&p->play_lock);
-		ast_mutex_destroy(&p->play_dtmf_lock);
-		p->readthread = 0;
-
-		vpb_close(p->handle);
-
-		iflist = iflist->next;
-
-		ast_free(p);
-	}
-	iflist = NULL;
-	ast_mutex_unlock(&iflock);
-
-	if (bridges) {
-		ast_mutex_lock(&bridge_lock);
-		for (int i = 0; i < max_bridges; i++) {
-			ast_mutex_destroy(&bridges[i].lock);
-			ast_cond_destroy(&bridges[i].cond);
-		}
-		ast_free(bridges);
-		bridges = NULL;
-		ast_mutex_unlock(&bridge_lock);
-	}
-
-	ao2_cleanup(vpb_tech.capabilities);
-	vpb_tech.capabilities = NULL;
-	ao2_cleanup(vpb_tech_indicate.capabilities);
-	vpb_tech_indicate.capabilities = NULL;
-	return 0;
-}
-
-/*!
- * \brief Load the module
- *
- * Module loading including tests for configuration or dependencies.
- * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
- * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
- * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the
- * configuration file or other non-critical problem return
- * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
- */
-static enum ast_module_load_result load_module()
-{
-	struct ast_config *cfg;
-	struct ast_variable *v;
-	struct ast_flags config_flags = { 0 };
-	struct vpb_pvt *tmp;
-	int board = 0, group = 0;
-	ast_group_t	callgroup = 0;
-	ast_group_t	pickupgroup = 0;
-	int mode = MODE_IMMEDIATE;
-	float txgain = DEFAULT_GAIN, rxgain = DEFAULT_GAIN;
-	float txswgain = 0, rxswgain = 0;
-	int got_gain=0;
-	int first_channel = 1;
-	int echo_cancel = DEFAULT_ECHO_CANCEL;
-	enum ast_module_load_result error = AST_MODULE_LOAD_SUCCESS; /* Error flag */
-	int bal1 = -1; /* Special value - means do not set */
-	int bal2 = -1;
-	int bal3 = -1;
-	char * callerid = NULL;
-	int num_cards = 0;
-
-	vpb_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
-	if (!vpb_tech.capabilities) {
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	vpb_tech_indicate.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
-	if (!vpb_tech_indicate.capabilities) {
-		return AST_MODULE_LOAD_DECLINE;
-	}
-	ast_format_cap_append(vpb_tech.capabilities, ast_format_slin, 0);
-	ast_format_cap_append(vpb_tech_indicate.capabilities, ast_format_slin, 0);
-	try {
-		num_cards = vpb_get_num_cards();
-	} catch (std::exception&) {
-		ast_log(LOG_ERROR, "No Voicetronix cards detected\n");
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	int ports_per_card[num_cards];
-	for (int i = 0; i < num_cards; ++i)
-		ports_per_card[i] = vpb_get_ports_per_card(i);
-
-	cfg = ast_config_load(config, config_flags);
-
-	/* We *must* have a config file otherwise stop immediately */
-	if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
-		ast_log(LOG_ERROR, "Unable to load config %s\n", config);
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	ast_mutex_lock(&iflock);
-	v = ast_variable_browse(cfg, "general");
-	while (v){
-		if (strcasecmp(v->name, "cards") == 0) {
-			ast_log(LOG_NOTICE, "VPB Driver configured to use [%d] cards\n", atoi(v->value));
-		} else if (strcasecmp(v->name, "indication") == 0) {
-			use_ast_ind = 1;
-			ast_log(LOG_NOTICE, "VPB driver using Asterisk Indication functions!\n");
-		} else if (strcasecmp(v->name, "break-for-dtmf") == 0) {
-			if (ast_true(v->value)) {
-				break_for_dtmf = 1;
-			} else {
-				break_for_dtmf = 0;
-				ast_log(LOG_NOTICE, "VPB driver not stopping for DTMF's in native bridge\n");
-			}
-		} else if (strcasecmp(v->name, "ast-dtmf") == 0) {
-			use_ast_dtmf = 1;
-			ast_log(LOG_NOTICE, "VPB driver using Asterisk DTMF play functions!\n");
-		} else if (strcasecmp(v->name, "ast-dtmf-det") == 0) {
-			use_ast_dtmfdet = 1;
-			ast_log(LOG_NOTICE, "VPB driver using Asterisk DTMF detection functions!\n");
-		} else if (strcasecmp(v->name, "relaxdtmf") == 0) {
-			relaxdtmf = 1;
-			ast_log(LOG_NOTICE, "VPB driver using Relaxed DTMF with Asterisk DTMF detections functions!\n");
-		} else if (strcasecmp(v->name, "timer_period_ring") == 0) {
-			timer_period_ring = atoi(v->value);
-		} else if (strcasecmp(v->name, "ecsuppthres") == 0) {
-			ec_supp_threshold = (short)atoi(v->value);
-		} else if (strcasecmp(v->name, "dtmfidd") == 0) {
-			dtmf_idd = atoi(v->value);
-			ast_log(LOG_NOTICE, "VPB Driver setting DTMF IDD to [%d]ms\n", dtmf_idd);
-		}
-		v = v->next;
-	}
-
-	v = ast_variable_browse(cfg, "interfaces");
-	while (v) {
-		/* Create the interface list */
-		if (strcasecmp(v->name, "board") == 0) {
-			board = atoi(v->value);
-		} else if (strcasecmp(v->name, "group") == 0) {
-			group = atoi(v->value);
-		} else if (strcasecmp(v->name, "callgroup") == 0) {
-			callgroup = ast_get_group(v->value);
-		} else if (strcasecmp(v->name, "pickupgroup") == 0) {
-			pickupgroup = ast_get_group(v->value);
-		} else if (strcasecmp(v->name, "usepolaritycid") == 0) {
-			UsePolarityCID = atoi(v->value);
-		} else if (strcasecmp(v->name, "useloopdrop") == 0) {
-			UseLoopDrop = atoi(v->value);
-		} else if (strcasecmp(v->name, "usenativebridge") == 0) {
-			UseNativeBridge = atoi(v->value);
-		} else if (strcasecmp(v->name, "channel") == 0) {
-			int channel = atoi(v->value);
-			if (board >= num_cards || board < 0 || channel < 0 || channel >= ports_per_card[board]) {
-				ast_log(LOG_ERROR, "Invalid board/channel (%d/%d) for channel '%s'\n", board, channel, v->value);
-				error = AST_MODULE_LOAD_FAILURE;
-				goto done;
-			}
-			tmp = mkif(board, channel, mode, got_gain, txgain, rxgain, txswgain, rxswgain, bal1, bal2, bal3, callerid, echo_cancel,group,callgroup,pickupgroup);
-			if (tmp) {
-				if (first_channel) {
-					mkbrd(tmp->vpb_model, echo_cancel);
-					first_channel = 0;
-				}
-				tmp->next = iflist;
-				iflist = tmp;
-			} else {
-				ast_log(LOG_ERROR, "Unable to register channel '%s'\n", v->value);
-				error = AST_MODULE_LOAD_FAILURE;
-				goto done;
-			}
-		} else if (strcasecmp(v->name, "language") == 0) {
-			ast_copy_string(language, v->value, sizeof(language));
-		} else if (strcasecmp(v->name, "callerid") == 0) {
-			callerid = ast_strdup(v->value);
-		} else if (strcasecmp(v->name, "mode") == 0) {
-			if (strncasecmp(v->value, "di", 2) == 0) {
-				mode = MODE_DIALTONE;
-			} else if (strncasecmp(v->value, "im", 2) == 0) {
-				mode = MODE_IMMEDIATE;
-			} else if (strncasecmp(v->value, "fx", 2) == 0) {
-				mode = MODE_FXO;
-			} else {
-				ast_log(LOG_WARNING, "Unknown mode: %s\n", v->value);
-			}
-		} else if (!strcasecmp(v->name, "context")) {
-			ast_copy_string(context, v->value, sizeof(context));
-		} else if (!strcasecmp(v->name, "echocancel")) {
-			if (!strcasecmp(v->value, "off")) {
-				echo_cancel = 0;
-			}
-		} else if (strcasecmp(v->name, "txgain") == 0) {
-			txswgain = parse_gain_value(v->name, v->value);
-			got_gain |= VPB_GOT_TXSWG;
-		} else if (strcasecmp(v->name, "rxgain") == 0) {
-			rxswgain = parse_gain_value(v->name, v->value);
-			got_gain |= VPB_GOT_RXSWG;
-		} else if (strcasecmp(v->name, "txhwgain") == 0) {
-			txgain = parse_gain_value(v->name, v->value);
-			got_gain |= VPB_GOT_TXHWG;
-		} else if (strcasecmp(v->name, "rxhwgain") == 0) {
-			rxgain = parse_gain_value(v->name, v->value);
-			got_gain |= VPB_GOT_RXHWG;
-		} else if (strcasecmp(v->name, "bal1") == 0) {
-			bal1 = strtol(v->value, NULL, 16);
-			if (bal1 < 0 || bal1 > 255) {
-				ast_log(LOG_WARNING, "Bad bal1 value: %d\n", bal1);
-				bal1 = -1;
-			}
-		} else if (strcasecmp(v->name, "bal2") == 0) {
-			bal2 = strtol(v->value, NULL, 16);
-			if (bal2 < 0 || bal2 > 255) {
-				ast_log(LOG_WARNING, "Bad bal2 value: %d\n", bal2);
-				bal2 = -1;
-			}
-		} else if (strcasecmp(v->name, "bal3") == 0) {
-			bal3 = strtol(v->value, NULL, 16);
-			if (bal3 < 0 || bal3 > 255) {
-				ast_log(LOG_WARNING, "Bad bal3 value: %d\n", bal3);
-				bal3 = -1;
-			}
-		} else if (strcasecmp(v->name, "grunttimeout") == 0) {
-			gruntdetect_timeout = 1000 * atoi(v->value);
-		}
-		v = v->next;
-	}
-
-	if (gruntdetect_timeout < 1000)
-		gruntdetect_timeout = 1000;
-
-	done: (void)0;
-	ast_mutex_unlock(&iflock);
-
-	ast_config_destroy(cfg);
-
-	if (use_ast_ind == 1) {
-		if (error == AST_MODULE_LOAD_SUCCESS && ast_channel_register(&vpb_tech_indicate) != 0) {
-			ast_log(LOG_ERROR, "Unable to register channel class 'vpb'\n");
-			error = AST_MODULE_LOAD_FAILURE;
-		} else {
-			ast_log(LOG_NOTICE, "VPB driver Registered (w/AstIndication)\n");
-		}
-	} else {
-		if (error == AST_MODULE_LOAD_SUCCESS && ast_channel_register(&vpb_tech) != 0) {
-			ast_log(LOG_ERROR, "Unable to register channel class 'vpb'\n");
-			error = AST_MODULE_LOAD_FAILURE;
-		} else {
-			ast_log(LOG_NOTICE, "VPB driver Registered )\n");
-		}
-	}
-
-
-	if (error != AST_MODULE_LOAD_SUCCESS)
-		unload_module();
-	else
-		restart_monitor(); /* And start the monitor for the first time */
-
-	return error;
-}
-
-/**/
-#if defined(__cplusplus) || defined(c_plusplus)
- }
-#endif
-/**/
-
-AST_MODULE_INFO_STANDARD_DEPRECATED(ASTERISK_GPL_KEY, "Voicetronix API driver");
diff --git a/channels/iax2/codec_pref.c b/channels/iax2/codec_pref.c
index 21cc8b34dea3a029d116f082cf15448f116d26f0..cadc92f2dfbeb41636050629e870755cd4d070c1 100644
--- a/channels/iax2/codec_pref.c
+++ b/channels/iax2/codec_pref.c
@@ -348,7 +348,7 @@ static const uint64_t iax2_supported_formats[] = {
 	AST_FORMAT_T140,
 	AST_FORMAT_SIREN7,
 	AST_FORMAT_SIREN14,
-	AST_FORMAT_TESTLAW,
+	0, /* reserved; was AST_FORMAT_TESTLAW */
 	AST_FORMAT_G719,
 	0, /* Place holder */
 	0, /* Place holder */
diff --git a/channels/iax2/format_compatibility.c b/channels/iax2/format_compatibility.c
index 1543792a9d74d01ce9022aaa42fec725ef956ac3..2325dd83e75c9c0137ba5fd20f89bf7c89955550 100644
--- a/channels/iax2/format_compatibility.c
+++ b/channels/iax2/format_compatibility.c
@@ -91,7 +91,6 @@ uint64_t iax2_format_compatibility_best(uint64_t formats)
 		AST_FORMAT_G719,
 		AST_FORMAT_SIREN14,
 		AST_FORMAT_SIREN7,
-		AST_FORMAT_TESTLAW,
 		/*! G.722 is better then all below, but not as common as the above... so give ulaw and alaw priority */
 		AST_FORMAT_G722,
 		/*! Okay, well, signed linear is easy to translate into other stuff */
diff --git a/channels/iax2/include/iax2.h b/channels/iax2/include/iax2.h
index e9dc96757238dd49981bd2ebf8f0ff1994fbbbef..0d92674833262c9c80c4f2dad90b13d23599a262 100644
--- a/channels/iax2/include/iax2.h
+++ b/channels/iax2/include/iax2.h
@@ -75,7 +75,7 @@ enum iax_frame_subclass {
 	IAX_COMMAND_VNAK =      18,
 	/*! Request status of a dialplan entry */
 	IAX_COMMAND_DPREQ =     19,
-	/*! Request status of a dialplan entry */
+	/*! Status reply of a dialplan entry status request */
 	IAX_COMMAND_DPREP =     20,
 	/*! Request a dial on channel brought up TBD */
 	IAX_COMMAND_DIAL =      21,
diff --git a/channels/misdn/Makefile b/channels/misdn/Makefile
deleted file mode 100644
index 96d5a2a3d2ea364ed2d0c3aeaca491ea8eca8c8b..0000000000000000000000000000000000000000
--- a/channels/misdn/Makefile
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Makefile for chan_misdn support
-#
-ifneq ($(wildcard /usr/include/linux/mISDNdsp.h),)
-CFLAGS+=-DMISDN_1_2
-endif
-
-all:
-
-%.o: %.c
-	$(CC) $(CFLAGS) -c -o $@ $<
-
-portinfo: portinfo.o
-	$(CC) -o $@ $^ -lisdnnet -lmISDN -lpthread
-
-clean:
-	rm -rf *.a *.o *.so portinfo *.i *.gcda *.gcno
diff --git a/channels/misdn/chan_misdn_config.h b/channels/misdn/chan_misdn_config.h
deleted file mode 100644
index c4054a87bb3804b0c6ce1747db304fc6322c5c27..0000000000000000000000000000000000000000
--- a/channels/misdn/chan_misdn_config.h
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Chan_Misdn -- Channel Driver for Asterisk
- *
- * Interface to mISDN
- *
- * Copyright (C) 2004, Christian Richter
- *
- * Christian Richter <crich@beronet.com>
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License
- */
-
-/*! \file
- * \brief Interface to mISDN - Config
- * \author Christian Richter <crich@beronet.com>
- */
-
-
-
-
-#ifndef CHAN_MISDN_CONFIG_H
-#define CHAN_MISDN_CONFIG_H
-
-#define BUFFERSIZE 512
-
-enum misdn_cfg_elements {
-
-	/* port config items */
-	MISDN_CFG_FIRST = 0,
-	MISDN_CFG_GROUPNAME,           /* char[] */
-	MISDN_CFG_ALLOWED_BEARERS,           /* char[] */
-	MISDN_CFG_FAR_ALERTING,        /* int (bool) */
-	MISDN_CFG_RXGAIN,              /* int */
-	MISDN_CFG_TXGAIN,              /* int */
-	MISDN_CFG_TE_CHOOSE_CHANNEL,   /* int (bool) */
-	MISDN_CFG_PMP_L1_CHECK,        /* int (bool) */
-	MISDN_CFG_REJECT_CAUSE,		/* int */
-	MISDN_CFG_ALARM_BLOCK,        /* int (bool) */
-	MISDN_CFG_HDLC,                /* int (bool) */
-	MISDN_CFG_CONTEXT,             /* char[] */
-	MISDN_CFG_LANGUAGE,            /* char[] */
-	MISDN_CFG_MUSICCLASS,            /* char[] */
-	MISDN_CFG_CALLERID,            /* char[] */
-	MISDN_CFG_INCOMING_CALLERID_TAG, /* char[] */
-	MISDN_CFG_APPEND_MSN_TO_CALLERID_TAG, /* int (bool) */
-	MISDN_CFG_METHOD,              /* char[] */
-	MISDN_CFG_DIALPLAN,            /* int */
-	MISDN_CFG_LOCALDIALPLAN,       /* int */
-	MISDN_CFG_CPNDIALPLAN,       /* int */
-	MISDN_CFG_TON_PREFIX_UNKNOWN,         /* char[] */
-	MISDN_CFG_TON_PREFIX_INTERNATIONAL,   /* char[] */
-	MISDN_CFG_TON_PREFIX_NATIONAL,        /* char[] */
-	MISDN_CFG_TON_PREFIX_NETWORK_SPECIFIC,/* char[] */
-	MISDN_CFG_TON_PREFIX_SUBSCRIBER,      /* char[] */
-	MISDN_CFG_TON_PREFIX_ABBREVIATED,     /* char[] */
-	MISDN_CFG_PRES,                /* int */
-	MISDN_CFG_SCREEN,              /* int */
-	MISDN_CFG_DISPLAY_CONNECTED,   /* int */
-	MISDN_CFG_DISPLAY_SETUP,       /* int */
-	MISDN_CFG_ALWAYS_IMMEDIATE,    /* int (bool) */
-	MISDN_CFG_NODIALTONE,    /* int (bool) */
-	MISDN_CFG_IMMEDIATE,           /* int (bool) */
-	MISDN_CFG_SENDDTMF,           /* int (bool) */
-	MISDN_CFG_ASTDTMF,            /* int (bool) */
-	MISDN_CFG_HOLD_ALLOWED,        /* int (bool) */
-	MISDN_CFG_EARLY_BCONNECT,      /* int (bool) */
-	MISDN_CFG_INCOMING_EARLY_AUDIO,      /* int (bool) */
-	MISDN_CFG_ECHOCANCEL,          /* int */
-	MISDN_CFG_CC_REQUEST_RETENTION,/* bool */
-	MISDN_CFG_OUTGOING_COLP,       /* int */
-#ifdef MISDN_1_2
-	MISDN_CFG_PIPELINE,      /* char[] */
-#endif
-
-#ifdef WITH_BEROEC
-	MISDN_CFG_BNECHOCANCEL,
-	MISDN_CFG_BNEC_ANTIHOWL,
-	MISDN_CFG_BNEC_NLP,
-	MISDN_CFG_BNEC_ZEROCOEFF,
-	MISDN_CFG_BNEC_TD,
-	MISDN_CFG_BNEC_ADAPT,
-#endif
-	MISDN_CFG_NEED_MORE_INFOS,     /* bool */
-	MISDN_CFG_NOAUTORESPOND_ON_SETUP,     /* bool */
-	MISDN_CFG_NTTIMEOUT,     	/* bool */
-	MISDN_CFG_BRIDGING,              /* bool */
-	MISDN_CFG_JITTERBUFFER,             /* int */
-	MISDN_CFG_JITTERBUFFER_UPPER_THRESHOLD,              /* int */
-	MISDN_CFG_CALLGROUP,           /* ast_group_t */
-	MISDN_CFG_PICKUPGROUP,         /* ast_group_t */
-	MISDN_CFG_NAMEDCALLGROUP,      /* ast_namedgroups * */
-	MISDN_CFG_NAMEDPICKUPGROUP,    /* ast_namedgroups * */
-	MISDN_CFG_MAX_IN,              /* int */
-	MISDN_CFG_MAX_OUT,              /* int */
-	MISDN_CFG_L1_TIMEOUT,          /* int */
-	MISDN_CFG_OVERLAP_DIAL, 	/* int (bool)*/
-	MISDN_CFG_MSNS,                /* char[] */
-	MISDN_CFG_FAXDETECT,           /* char[] */
- 	MISDN_CFG_FAXDETECT_CONTEXT,   /* char[] */
- 	MISDN_CFG_FAXDETECT_TIMEOUT,   /* int */
-	MISDN_CFG_PTP,                 /* int (bool) */
-	MISDN_CFG_LAST,
-
-	/* general config items */
-	MISDN_GEN_FIRST,
-#ifndef MISDN_1_2
-	MISDN_GEN_MISDN_INIT,           /* char[] */
-#endif
-	MISDN_GEN_DEBUG,               /* int */
-	MISDN_GEN_TRACEFILE,           /* char[] */
-	MISDN_GEN_BRIDGING,            /* int (bool) */
-	MISDN_GEN_STOP_TONE,           /* int (bool) */
-	MISDN_GEN_APPEND_DIGITS2EXTEN, /* int (bool) */
-	MISDN_GEN_DYNAMIC_CRYPT,       /* int (bool) */
-	MISDN_GEN_CRYPT_PREFIX,        /* char[] */
-	MISDN_GEN_CRYPT_KEYS,          /* char[] */
-	MISDN_GEN_NTKEEPCALLS,          /* int (bool) */
-	MISDN_GEN_NTDEBUGFLAGS,          /* int */
-	MISDN_GEN_NTDEBUGFILE,          /* char[] */
-	MISDN_GEN_LAST
-};
-
-enum misdn_cfg_method {
-	METHOD_STANDARD = 0,
-	METHOD_ROUND_ROBIN,
-	METHOD_STANDARD_DEC
-};
-
-/* you must call misdn_cfg_init before any other function of this header file */
-int misdn_cfg_init(int max_ports, int reload);
-void misdn_cfg_reload(void);
-void misdn_cfg_destroy(void);
-
-void misdn_cfg_update_ptp( void );
-
-/* if you requst a general config element, the port value is ignored. if the requested
- * value is not available, or the buffer is too small, the buffer will be nulled (in
- * case of a char* only its first byte will be nulled). */
-void misdn_cfg_get(int port, enum misdn_cfg_elements elem, void* buf, int bufsize);
-
-/* returns the enum element for the given name, returns MISDN_CFG_FIRST if none was found */
-enum misdn_cfg_elements misdn_cfg_get_elem (const char *name);
-
-/* fills the buffer with the name of the given config element */
-void misdn_cfg_get_name (enum misdn_cfg_elements elem, void *buf, int bufsize);
-
-/* fills the buffer with the description of the given config element */
-void misdn_cfg_get_desc (enum misdn_cfg_elements elem, void *buf, int bufsize, void *buf_default, int bufsize_default);
-
-/* fills the buffer with a ',' separated list of all active ports */
-void misdn_cfg_get_ports_string(char *ports);
-
-/* fills the buffer with a nice printable string representation of the config element */
-void misdn_cfg_get_config_string(int port, enum misdn_cfg_elements elem, char* buf, int bufsize);
-
-/* returns the next available port number. returns -1 if the last one was reached. */
-int misdn_cfg_get_next_port(int port);
-int misdn_cfg_get_next_port_spin(int port);
-
-int misdn_cfg_is_msn_valid(int port, char* msn);
-int misdn_cfg_is_port_valid(int port);
-int misdn_cfg_is_group_method(char *group, enum misdn_cfg_method meth);
-
-#if 0
-char *misdn_cfg_get_next_group(char *group);
-int misdn_cfg_get_next_port_in_group(int port, char *group);
-#endif
-
-struct ast_jb_conf *misdn_get_global_jbconf(void);
-
-#endif
diff --git a/channels/misdn/ie.c b/channels/misdn/ie.c
deleted file mode 100644
index 67fc9585e87506ba64f32c5e1bf58b2436021c2a..0000000000000000000000000000000000000000
--- a/channels/misdn/ie.c
+++ /dev/null
@@ -1,1414 +0,0 @@
-/*
- * Chan_Misdn -- Channel Driver for Asterisk
- *
- * Interface to mISDN
- *
- * Copyright (C) 2005, Christian Richter
- *
- * Christian Richter <crich@beronet.com>
- *
- * heaviliy patched from jollys ie.cpp, jolly gave me ALL
- * rights for this code, i can even have my own copyright on it.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License
- */
-
-/*! \file
- * \brief Interface to mISDN
- * \author Christian Richter <crich@beronet.com>
- */
-
-/*
-  the pointer of enc_ie_* always points to the IE itself
-  if qi is not NULL (TE-mode), offset is set
-*/
-
-/*** MODULEINFO
-	<support_level>extended</support_level>
- ***/
-
-#include "asterisk.h"
-
-#include <string.h>
-
-#include <mISDNuser/mISDNlib.h>
-#include <mISDNuser/isdn_net.h>
-#include <mISDNuser/l3dss1.h>
-#include <mISDNuser/net_l3.h>
-#include "asterisk/localtime.h"
-
-
-
-#define MISDN_IE_DEBG 0
-
-/* support stuff */
-static void strnncpy(char *dest, const char *src, size_t len, size_t dst_len)
-{
-	if (len > dst_len-1)
-		len = dst_len-1;
-	strncpy(dest, src, len);
-	dest[len] = '\0';
-}
-
-
-/* IE_COMPLETE */
-static void enc_ie_complete(unsigned char **ntmode, msg_t *msg, int complete, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-
-	if (complete<0 || complete>1)
-	{
-		printf("%s: ERROR: complete(%d) is out of range.\n", __FUNCTION__, complete);
-		return;
-	}
-
-	if (complete)
-		if (MISDN_IE_DEBG) printf("    complete=%d\n", complete);
-
-	if (complete)
-	{
-		p = msg_put(msg, 1);
-		if (nt)
-		{
-			*ntmode = p;
-		} else
-			qi->QI_ELEMENT(sending_complete) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-
-		p[0] = IE_COMPLETE;
-	}
-}
-
-static void dec_ie_complete(unsigned char *p, Q931_info_t *qi, int *complete, int nt, struct misdn_bchannel *bc)
-{
-	*complete = 0;
-	if (!nt)
-	{
-		if (qi->QI_ELEMENT(sending_complete))
-			*complete = 1;
-	} else
-		if (p)
-			*complete = 1;
-
-	if (*complete)
-		if (MISDN_IE_DEBG) printf("    complete=%d\n", *complete);
-}
-
-
-/* IE_BEARER */
-static void enc_ie_bearer(unsigned char **ntmode, msg_t *msg, int coding, int capability, int mode, int rate, int multi, int user, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-
-	if (coding<0 || coding>3)
-	{
-		printf("%s: ERROR: coding(%d) is out of range.\n", __FUNCTION__, coding);
-		return;
-	}
-	if (capability<0 || capability>31)
-	{
-		printf("%s: ERROR: capability(%d) is out of range.\n", __FUNCTION__, capability);
-		return;
-	}
-	if (mode<0 || mode>3)
-	{
-		printf("%s: ERROR: mode(%d) is out of range.\n", __FUNCTION__, mode);
-		return;
-	}
-	if (rate<0 || rate>31)
-	{
-		printf("%s: ERROR: rate(%d) is out of range.\n", __FUNCTION__, rate);
-		return;
-	}
-	if (multi>127)
-	{
-		printf("%s: ERROR: multi(%d) is out of range.\n", __FUNCTION__, multi);
-		return;
-	}
-	if (user>31)
-	{
-		printf("%s: ERROR: user L1(%d) is out of range.\n", __FUNCTION__, rate);
-		return;
-	}
-	if (rate!=24 && multi>=0)
-	{
-		printf("%s: WARNING: multi(%d) is only possible if rate(%d) would be 24.\n", __FUNCTION__, multi, rate);
-		multi = -1;
-	}
-
-	if (MISDN_IE_DEBG) printf("    coding=%d capability=%d mode=%d rate=%d multi=%d user=%d\n", coding, capability, mode, rate, multi, user);
-
-	l = 2 + (multi>=0) + (user>=0);
-	p = msg_put(msg, l+2);
-	if (nt)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(bearer_capability) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_BEARER;
-	p[1] = l;
-	p[2] = 0x80 + (coding<<5) + capability;
-	p[3] = 0x80 + (mode<<5) + rate;
-	if (multi >= 0)
-		p[4] = 0x80 + multi;
-	if (user >= 0)
-		p[4+(multi>=0)] = 0xa0 + user;
-}
-
-static void dec_ie_bearer(unsigned char *p, Q931_info_t *qi, int *coding, int *capability, int *mode, int *rate, int *multi, int *user,
-		   int *async, int *urate, int *stopbits, int *dbits, int *parity, int nt, struct misdn_bchannel *bc)
-{
-	int octet;
-	*coding = -1;
-	*capability = -1;
-	*mode = -1;
-	*rate = -1;
-	*multi = -1;
-	*user = -1;
-	*async = -1;
-	*urate = -1;
-	*stopbits = -1;
-	*dbits = -1;
-	*parity = -1;
-
-	if (!nt)
-	{
-		p = NULL;
-#ifdef LLC_SUPPORT
-		if (qi->QI_ELEMENT(llc)) {
-
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(llc) + 1;
-		}
-#endif
-		if (qi->QI_ELEMENT(bearer_capability))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(bearer_capability) + 1;
-	}
-	if (!p)
-		return;
-
-	if (p[0] < 2)
-	{
-		printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-		return;
-	}
-
-	*coding = (p[1]&0x60) >> 5;
-	*capability = p[1] & 0x1f;
-	octet = 2;
-	if (!(p[1] & 0x80))
-		octet++;
-
-	if (p[0] < octet)
-		goto done;
-
-	*mode = (p[octet]&0x60) >> 5;
-	*rate = p[octet] & 0x1f;
-
-	octet++;
-
-	if (p[0] < octet)
-		goto done;
-
-	if (*rate == 0x18) {
-		/* Rate multiplier only present if 64Kb/s base rate */
-		*multi = p[octet++] & 0x7f;
-	}
-
-	if (p[0] < octet)
-		goto done;
-
-	/* Start L1 info */
-	if ((p[octet] & 0x60) == 0x20) {
-		*user = p[octet] & 0x1f;
-
-		if (p[0] <= octet)
-			goto done;
-
-		if (p[octet++] & 0x80)
-			goto l2;
-
-		*async = !!(p[octet] & 0x40);
-		/* 0x20 is inband negotiation */
-		*urate = p[octet] & 0x1f;
-
-		if (p[0] <= octet)
-			goto done;
-
-		if (p[octet++] & 0x80)
-			goto l2;
-
-		/* Ignore next byte for now: Intermediate rate, NIC, flow control */
-
-		if (p[0] <= octet)
-			goto done;
-
-		if (p[octet++] & 0x80)
-			goto l2;
-
-		/* And the next one. Header, multiframe, mode, assignor/ee, negotiation */
-
-		if (p[0] <= octet)
-			goto done;
-
-		if (~p[octet++] & 0x80)
-			goto l2;
-
-		/* Wheee. V.110 speed information */
-
-		*stopbits = (p[octet] & 0x60) >> 5;
-		*dbits = (p[octet] & 0x18) >> 3;
-		*parity = p[octet] & 7;
-
-		octet++;
-	}
- l2: /* Nobody seems to want the rest so we don't bother (yet) */
- done:
-	if (MISDN_IE_DEBG) printf("    coding=%d capability=%d mode=%d rate=%d multi=%d user=%d async=%d urate=%d stopbits=%d dbits=%d parity=%d\n", *coding, *capability, *mode, *rate, *multi, *user, *async, *urate, *stopbits, *dbits, *parity);
-}
-
-
-/* IE_CALL_ID */
-#if 0
-static void enc_ie_call_id(unsigned char **ntmode, msg_t *msg, char *callid, int callid_len, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-
-	char debug[25];
-	int i;
-
-	if (!callid || callid_len<=0)
-	{
-		return;
-	}
-	if (callid_len>8)
-	{
-		printf("%s: ERROR: callid_len(%d) is out of range.\n", __FUNCTION__, callid_len);
-		return;
-	}
-
-	i = 0;
-	while(i < callid_len)
-	{
-		if (MISDN_IE_DEBG) printf(debug+(i*3), " %02hhx", (unsigned char)callid[i]);
-		i++;
-	}
-
-	if (MISDN_IE_DEBG) printf("    callid%s\n", debug);
-
-	l = callid_len;
-	p = msg_put(msg, l+2);
-	if (nt)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(call_id) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_CALL_ID;
-	p[1] = l;
-	memcpy(p+2, callid, callid_len);
-}
-#endif
-
-#if 0
-static void dec_ie_call_id(unsigned char *p, Q931_info_t *qi, char *callid, int *callid_len, int nt, struct misdn_bchannel *bc)
-{
-	char debug[25];
-	int i;
-
-	*callid_len = -1;
-
-	if (!nt)
-	{
-		p = NULL;
-		if (qi->QI_ELEMENT(call_id))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(call_id) + 1;
-	}
-	if (!p)
-		return;
-	if (p[0] > 8)
-	{
-		printf("%s: ERROR: IE too long (%d).\n", __FUNCTION__, p[0]);
-		return;
-	}
-
-	*callid_len = p[0];
-	memcpy(callid, p+1, *callid_len);
-
-	i = 0;
-	while(i < *callid_len)
-	{
-		if (MISDN_IE_DEBG) printf(debug+(i*3), " %02hhx", (unsigned char)callid[i]);
-		i++;
-	}
-
-	if (MISDN_IE_DEBG) printf("    callid%s\n", debug);
-}
-#endif
-
-/* IE_CALLED_PN */
-static void enc_ie_called_pn(unsigned char **ntmode, msg_t *msg, int type, int plan, char *number, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-
-	if (type<0 || type>7)
-	{
-		printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type);
-		return;
-	}
-	if (plan<0 || plan>15)
-	{
-		printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan);
-		return;
-	}
-	if (!number[0])
-	{
-		printf("%s: ERROR: number is not given.\n", __FUNCTION__);
-		return;
-	}
-
-	if (MISDN_IE_DEBG) printf("    type=%d plan=%d number='%s'\n", type, plan, number);
-
-	l = 1+strlen((char *)number);
-	p = msg_put(msg, l+2);
-	if (nt)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(called_nr) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_CALLED_PN;
-	p[1] = l;
-	p[2] = 0x80 + (type<<4) + plan;
-	strncpy((char *)p+3, (char *)number, strlen((char *)number));
-}
-
-static void dec_ie_called_pn(unsigned char *p, Q931_info_t *qi, int *type, int *plan, char *number, size_t number_len, int nt, struct misdn_bchannel *bc)
-{
-	*type = -1;
-	*plan = -1;
-	*number = '\0';
-
-	if (!nt)
-	{
-		p = NULL;
-		if (qi->QI_ELEMENT(called_nr))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(called_nr) + 1;
-	}
-	if (!p)
-		return;
-	if (p[0] < 2)
-	{
-		printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-		return;
-	}
-
-	*type = (p[1]&0x70) >> 4;
-	*plan = p[1] & 0xf;
-	strnncpy(number, (char *)p+2, p[0]-1, number_len);
-
-	if (MISDN_IE_DEBG) printf("    type=%d plan=%d number='%s'\n", *type, *plan, number);
-}
-
-
-/* IE_CALLING_PN */
-static void enc_ie_calling_pn(unsigned char **ntmode, msg_t *msg, int type, int plan, int present, int screen, char *number, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-
-	if (type<0 || type>7)
-	{
-		printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type);
-		return;
-	}
-	if (plan<0 || plan>15)
-	{
-		printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan);
-		return;
-	}
-	if (present>3)
-	{
-		printf("%s: ERROR: present(%d) is out of range.\n", __FUNCTION__, present);
-		return;
-	}
-	if (present >= 0) if (screen<0 || screen>3)
-	{
-		printf("%s: ERROR: screen(%d) is out of range.\n", __FUNCTION__, screen);
-		return;
-	}
-
-	if (MISDN_IE_DEBG) printf("    type=%d plan=%d present=%d screen=%d number='%s'\n", type, plan, present, screen, number);
-
-	l = 1;
-	if (number) if (number[0])
-		l += strlen((char *)number);
-	if (present >= 0)
-		l += 1;
-	p = msg_put(msg, l+2);
-	if (nt)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(calling_nr) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_CALLING_PN;
-	p[1] = l;
-	if (present >= 0)
-	{
-		p[2] = 0x00 + (type<<4) + plan;
-		p[3] = 0x80 + (present<<5) + screen;
-		if (number) if (number[0])
-			strncpy((char *)p+4, (char *)number, strlen((char *)number));
-	} else
-	{
-		p[2] = 0x80 + (type<<4) + plan;
-		if (number) if (number[0])
-			strncpy((char *)p+3, (char *)number, strlen((char *)number));
-	}
-}
-
-static void dec_ie_calling_pn(unsigned char *p, Q931_info_t *qi, int *type, int *plan, int *present, int *screen, char *number, size_t number_len, int nt, struct misdn_bchannel *bc)
-{
-	*type = -1;
-	*plan = -1;
-	*present = -1;
-	*screen = -1;
-	*number = '\0';
-
-	if (!nt)
-	{
-		p = NULL;
-		if (qi->QI_ELEMENT(calling_nr))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(calling_nr) + 1;
-	}
-	if (!p)
-		return;
-	if (p[0] < 1)
-	{
-		printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-		return;
-	}
-
-	*type = (p[1]&0x70) >> 4;
-	*plan = p[1] & 0xf;
-	if (!(p[1] & 0x80))
-	{
-		if (p[0] < 2)
-		{
-			printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-			return;
-		}
-		*present = (p[2]&0x60) >> 5;
-		*screen = p[2] & 0x3;
-		strnncpy(number, (char *)p+3, p[0]-2, number_len);
-	} else
-	{
-		strnncpy(number, (char *)p+2, p[0]-1, number_len);
- 		/* SPECIAL workarround for IBT software bug */
-		/* if (number[0]==0x80) */
-		/*  strcpy((char *)number, (char *)number+1); */
-	}
-
-	if (MISDN_IE_DEBG) printf("    type=%d plan=%d present=%d screen=%d number='%s'\n", *type, *plan, *present, *screen, number);
-}
-
-
-/* IE_CONNECTED_PN */
-static void enc_ie_connected_pn(unsigned char **ntmode, msg_t *msg, int type, int plan, int present, int screen, char *number, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-
-	if (type<0 || type>7)
-	{
-		printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type);
-		return;
-	}
-	if (plan<0 || plan>15)
-	{
-		printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan);
-		return;
-	}
-	if (present>3)
-	{
-		printf("%s: ERROR: present(%d) is out of range.\n", __FUNCTION__, present);
-		return;
-	}
-	if (present >= 0) if (screen<0 || screen>3)
-	{
-		printf("%s: ERROR: screen(%d) is out of range.\n", __FUNCTION__, screen);
-		return;
-	}
-
-	if (MISDN_IE_DEBG) printf("    type=%d plan=%d present=%d screen=%d number='%s'\n", type, plan, present, screen, number);
-
-	l = 1;
-	if (number) if (number[0])
-		l += strlen((char *)number);
-	if (present >= 0)
-		l += 1;
-	p = msg_put(msg, l+2);
-	if (nt)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(connected_nr) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_CONNECT_PN;
-	p[1] = l;
-	if (present >= 0)
-	{
-		p[2] = 0x00 + (type<<4) + plan;
-		p[3] = 0x80 + (present<<5) + screen;
-		if (number) if (number[0])
-			strncpy((char *)p+4, (char *)number, strlen((char *)number));
-	} else
-	{
-		p[2] = 0x80 + (type<<4) + plan;
-		if (number) if (number[0])
-			strncpy((char *)p+3, (char *)number, strlen((char *)number));
-	}
-}
-
-static void dec_ie_connected_pn(unsigned char *p, Q931_info_t *qi, int *type, int *plan, int *present, int *screen, char *number, size_t number_len, int nt, struct misdn_bchannel *bc)
-{
-	*type = -1;
-	*plan = -1;
-	*present = -1;
-	*screen = -1;
-	*number = '\0';
-
-	if (!nt)
-	{
-		p = NULL;
-		if (qi->QI_ELEMENT(connected_nr))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(connected_nr) + 1;
-	}
-	if (!p)
-		return;
-	if (p[0] < 1)
-	{
-		printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-		return;
-	}
-
-	*type = (p[1]&0x70) >> 4;
-	*plan = p[1] & 0xf;
-	if (!(p[1] & 0x80))
-	{
-		if (p[0] < 2)
-		{
-			printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-			return;
-		}
-		*present = (p[2]&0x60) >> 5;
-		*screen = p[2] & 0x3;
-		strnncpy(number, (char *)p+3, p[0]-2, number_len);
-	} else
-	{
-		strnncpy(number, (char *)p+2, p[0]-1, number_len);
-	}
-
-	if (MISDN_IE_DEBG) printf("    type=%d plan=%d present=%d screen=%d number='%s'\n", *type, *plan, *present, *screen, number);
-}
-
-
-/* IE_CAUSE */
-static void enc_ie_cause(unsigned char **ntmode, msg_t *msg, int location, int cause, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-
-	if (location<0 || location>7)
-	{
-		printf("%s: ERROR: location(%d) is out of range.\n", __FUNCTION__, location);
-		return;
-	}
-	if (cause<0 || cause>127)
-	{
-		printf("%s: ERROR: cause(%d) is out of range.\n", __FUNCTION__, cause);
-		return;
-	}
-
-	if (MISDN_IE_DEBG) printf("    location=%d cause=%d\n", location, cause);
-
-	l = 2;
-	p = msg_put(msg, l+2);
-	if (nt)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(cause) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_CAUSE;
-	p[1] = l;
-	p[2] = 0x80 + location;
-	p[3] = 0x80 + cause;
-}
-
-#if 0
-static void enc_ie_cause_standalone(unsigned char **ntmode, msg_t *msg, int location, int cause, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p = msg_put(msg, 4);
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	if (ntmode)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(cause) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_CAUSE;
-	p[1] = 2;
-	p[2] = 0x80 + location;
-	p[3] = 0x80 + cause;
-}
-#endif
-
-static void dec_ie_cause(unsigned char *p, Q931_info_t *qi, int *location, int *cause, int nt, struct misdn_bchannel *bc)
-{
-	*location = -1;
-	*cause = -1;
-
-	if (!nt)
-	{
-		p = NULL;
-		if (qi->QI_ELEMENT(cause))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(cause) + 1;
-	}
-	if (!p)
-		return;
-	if (p[0] < 2)
-	{
-		printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-		return;
-	}
-
-	*location = p[1] & 0x0f;
-	*cause = p[2] & 0x7f;
-
-	if (MISDN_IE_DEBG) printf("    location=%d cause=%d\n", *location, *cause);
-}
-
-
-/* IE_CHANNEL_ID */
-static void enc_ie_channel_id(unsigned char **ntmode, msg_t *msg, int exclusive, int channel, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-	int pri = stack->pri;
-
-	if (exclusive<0 || exclusive>1)
-	{
-		printf("%s: ERROR: exclusive(%d) is out of range.\n", __FUNCTION__, exclusive);
-		return;
-	}
-	if ((channel<0 || channel>0xff)
-	    || (!pri && (channel>2 && channel<0xff))
-	    || (pri && (channel>31 && channel<0xff))
-	    || (pri && channel==16))
-	{
-		printf("%s: ERROR: channel(%d) is out of range.\n", __FUNCTION__, channel);
-		return;
-	}
-
-	/* if (MISDN_IE_DEBG) printf("    exclusive=%d channel=%d\n", exclusive, channel); */
-
-
-	if (!pri)
-	{
-		/* BRI */
-		l = 1;
-		p = msg_put(msg, l+2);
-		if (nt)
-			*ntmode = p+1;
-		else
-			qi->QI_ELEMENT(channel_id) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-		p[0] = IE_CHANNEL_ID;
-		p[1] = l;
-		if (channel == 0xff)
-			channel = 3;
-		p[2] = 0x80 + (exclusive<<3) + channel;
-		/* printf("    exclusive=%d channel=%d\n", exclusive, channel); */
-	} else
-	{
-		/* PRI */
-		if (channel == 0) /* no channel */
-			return; /* IE not present */
-/* 		if (MISDN_IE_DEBG) printf("channel = %d\n", channel); */
-		if (channel == 0xff) /* any channel */
-		{
-			l = 1;
-			p = msg_put(msg, l+2);
-			if (nt)
-				*ntmode = p+1;
-			else
-				qi->QI_ELEMENT(channel_id) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-			p[0] = IE_CHANNEL_ID;
-			p[1] = l;
-			p[2] = 0x80 + 0x20 + 0x03;
-/* 			if (MISDN_IE_DEBG) printf("%02hhx\n", p[2]); */
-			return; /* end */
-		}
-		l = 3;
-		p = msg_put(msg, l+2);
-		if (nt)
-			*ntmode = p+1;
-		else
-			qi->QI_ELEMENT(channel_id) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-		p[0] = IE_CHANNEL_ID;
-		p[1] = l;
-		p[2] = 0x80 + 0x20 + (exclusive<<3) + 0x01;
-		p[3] = 0x80 + 3; /* CCITT, Number, B-type */
-		p[4] = 0x80 + channel;
-/* 		if (MISDN_IE_DEBG) printf("%02hhx %02hhx %02hhx\n", p[2], p[3], p[4]); */
-	}
-}
-
-static void dec_ie_channel_id(unsigned char *p, Q931_info_t *qi, int *exclusive, int *channel, int nt, struct misdn_bchannel *bc)
-{
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-	int pri =stack->pri;
-
-	*exclusive = -1;
-	*channel = -1;
-
-	if (!nt)
-	{
-		p = NULL;
-		if (qi->QI_ELEMENT(channel_id))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(channel_id) + 1;
-	}
-	if (!p)
-		return;
-	if (p[0] < 1)
-	{
-		printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-		return;
-	}
-
-	if (p[1] & 0x40)
-	{
-		printf("%s: ERROR: refering to channels of other interfaces is not supported.\n", __FUNCTION__);
-		return;
-	}
-	if (p[1] & 0x04)
-	{
-		printf("%s: ERROR: using d-channel is not supported.\n", __FUNCTION__);
-		return;
-	}
-
-	*exclusive = (p[1]&0x08) >> 3;
-	if (!pri)
-	{
-		/* BRI */
-		if (p[1] & 0x20)
-		{
-			printf("%s: ERROR: extended channel ID with non PRI interface.\n", __FUNCTION__);
-			return;
-		}
-		*channel = p[1] & 0x03;
-		if (*channel == 3)
-			*channel = 0xff;
-	} else
-	{
-		/* PRI */
-		if (p[0] < 1)
-		{
-			printf("%s: ERROR: IE too short for PRI (%d).\n", __FUNCTION__, p[0]);
-			return;
-		}
-		if (!(p[1] & 0x20))
-		{
-			printf("%s: ERROR: basic channel ID with PRI interface.\n", __FUNCTION__);
-			return;
-		}
-		if ((p[1]&0x03) == 0x00)
-		{
-			/* no channel */
-			*channel = 0;
-			return;
-		}
-		if ((p[1]&0x03) == 0x03)
-		{
-			/* any channel */
-			*channel = 0xff;
-			return;
-		}
-		if (p[0] < 3)
-		{
-			printf("%s: ERROR: IE too short for PRI with channel(%d).\n", __FUNCTION__, p[0]);
-			return;
-		}
-		if (p[2] & 0x10)
-		{
-			printf("%s: ERROR: channel map not supported.\n", __FUNCTION__);
-			return;
-		}
-		*channel = p[3] & 0x7f;
-		if ( (*channel<1) | (*channel==16) | (*channel>31))
-		{
-			printf("%s: ERROR: PRI interface channel out of range (%d).\n", __FUNCTION__, *channel);
-			return;
-		}
-/* 		if (MISDN_IE_DEBG) printf("%02hhx %02hhx %02hhx\n", p[1], p[2], p[3]); */
-	}
-
-	if (MISDN_IE_DEBG) printf("    exclusive=%d channel=%d\n", *exclusive, *channel);
-}
-
-
-/* IE_DATE */
-static void enc_ie_date(unsigned char **ntmode, msg_t *msg, time_t ti, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-	struct timeval tv = { ti, 0 };
-	struct ast_tm tm;
-
-	ast_localtime(&tv, &tm, NULL);
-	if (MISDN_IE_DEBG) printf("    year=%d month=%d day=%d hour=%d minute=%d\n", tm.tm_year%100, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min);
-
-	l = 5;
-	p = msg_put(msg, l+2);
-	if (nt)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(date) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_DATE;
-	p[1] = l;
-	p[2] = tm.tm_year % 100;
-	p[3] = tm.tm_mon + 1;
-	p[4] = tm.tm_mday;
-	p[5] = tm.tm_hour;
-	p[6] = tm.tm_min;
-}
-
-
-/* IE_DISPLAY */
-static void enc_ie_display(unsigned char **ntmode, msg_t *msg, char *display, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *) (msg->data + mISDN_HEADER_LEN);
-	int l;
-
-	if (!display[0])
-	{
-		printf("%s: ERROR: display text not given.\n", __FUNCTION__);
-		return;
-	}
-
-	l = strlen(display);
-	if (80 < l)
-	{
-		l = 80;
-		printf("%s: WARNING: display text too long (max %d chars), cutting.\n", __FUNCTION__, l);
-		display[l] = '\0';
-	}
-
-	/* if (MISDN_IE_DEBG) printf("    display='%s' (len=%d)\n", display, l); */
-
-	p = msg_put(msg, l + 2);
-	if (nt)
-		*ntmode = p + 1;
-	else
-		qi->QI_ELEMENT(display) = p - (unsigned char *) qi - sizeof(Q931_info_t);
-	p[0] = IE_DISPLAY;
-	p[1] = l;
-	strncpy((char *) p + 2, display, l);
-}
-
-#if 0
-static void dec_ie_display(unsigned char *p, Q931_info_t *qi, char *display, size_t display_len, int nt, struct misdn_bchannel *bc)
-{
-	*display = '\0';
-
-	if (!nt)
-	{
-		p = NULL;
-		if (qi->QI_ELEMENT(display))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(display) + 1;
-	}
-	if (!p)
-		return;
-	if (p[0] < 1)
-	{
-		printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-		return;
-	}
-
-	strnncpy(display, (char *)p+1, p[0], display_len);
-
-	if (MISDN_IE_DEBG) printf("    display='%s'\n", display);
-}
-#endif
-
-/* IE_KEYPAD */
-#if 1
-static void enc_ie_keypad(unsigned char **ntmode, msg_t *msg, char *keypad, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-
-	if (!keypad[0])
-	{
-		printf("%s: ERROR: keypad info not given.\n", __FUNCTION__);
-		return;
-	}
-
-	if (MISDN_IE_DEBG) printf("    keypad='%s'\n", keypad);
-
-	l = strlen(keypad);
-	p = msg_put(msg, l+2);
-	if (nt)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(keypad) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_KEYPAD;
-	p[1] = l;
-	strncpy((char *)p+2, keypad, strlen(keypad));
-}
-#endif
-
-static void dec_ie_keypad(unsigned char *p, Q931_info_t *qi, char *keypad, size_t keypad_len, int nt, struct misdn_bchannel *bc)
-{
-	*keypad = '\0';
-
-	if (!nt)
-	{
-		p = NULL;
-		if (qi->QI_ELEMENT(keypad))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(keypad) + 1;
-	}
-	if (!p)
-		return;
-	if (p[0] < 1)
-	{
-		printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-		return;
-	}
-
-	strnncpy(keypad, (char *)p+1, p[0], keypad_len);
-
-	if (MISDN_IE_DEBG) printf("    keypad='%s'\n", keypad);
-}
-
-
-/* IE_NOTIFY */
-static void enc_ie_notify(unsigned char **ntmode, msg_t *msg, int notify, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-
-	if (notify<0 || notify>0x7f)
-	{
-		printf("%s: ERROR: notify(%d) is out of range.\n", __FUNCTION__, notify);
-		return;
-	}
-
-	if (MISDN_IE_DEBG) printf("    notify=%d\n", notify);
-
-	l = 1;
-	p = msg_put(msg, l+2);
-	if (nt)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(notify) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_NOTIFY;
-	p[1] = l;
-	p[2] = 0x80 + notify;
-}
-
-static void dec_ie_notify(unsigned char *p, Q931_info_t *qi, int *notify, int nt, struct misdn_bchannel *bc)
-{
-	*notify = -1;
-
-	if (!nt)
-	{
-		p = NULL;
-		if (qi->QI_ELEMENT(notify))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(notify) + 1;
-	}
-	if (!p)
-		return;
-	if (p[0] < 1)
-	{
-		printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-		return;
-	}
-
-	*notify = p[1] & 0x7f;
-
-	if (MISDN_IE_DEBG) printf("    notify=%d\n", *notify);
-}
-
-
-/* IE_PROGRESS */
-static void enc_ie_progress(unsigned char **ntmode, msg_t *msg, int coding, int location, int progress, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-
-	if (coding<0 || coding>0x03)
-	{
-		printf("%s: ERROR: coding(%d) is out of range.\n", __FUNCTION__, coding);
-		return;
-	}
-	if (location<0 || location>0x0f)
-	{
-		printf("%s: ERROR: location(%d) is out of range.\n", __FUNCTION__, location);
-		return;
-	}
-	if (progress<0 || progress>0x7f)
-	{
-		printf("%s: ERROR: progress(%d) is out of range.\n", __FUNCTION__, progress);
-		return;
-	}
-
-	if (MISDN_IE_DEBG) printf("    coding=%d location=%d progress=%d\n", coding, location, progress);
-
-	l = 2;
-	p = msg_put(msg, l+2);
-	if (nt)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(progress) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_PROGRESS;
-	p[1] = l;
-	p[2] = 0x80 + (coding<<5) + location;
-	p[3] = 0x80 + progress;
-}
-
-static void dec_ie_progress(unsigned char *p, Q931_info_t *qi, int *coding, int *location, int *progress, int nt, struct misdn_bchannel *bc)
-{
-	*coding = -1;
-	*location = -1;
-	//*progress = -1;
-	*progress = 0;
-
-	if (!nt)
-	{
-		p = NULL;
-		if (qi->QI_ELEMENT(progress))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(progress) + 1;
-	}
-	if (!p)
-		return;
-	if (p[0] < 1)
-	{
-		printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-		return;
-	}
-
-	*coding = (p[1]&0x60) >> 5;
-	*location = p[1] & 0x0f;
-	*progress = p[2] & 0x7f;
-
-	if (MISDN_IE_DEBG) printf("    coding=%d location=%d progress=%d\n", *coding, *location, *progress);
-}
-
-
-/* IE_REDIR_NR (redirecting = during MT_SETUP) */
-static void enc_ie_redir_nr(unsigned char **ntmode, msg_t *msg, int type, int plan, int present, int screen, int reason, char *number, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-
-	if (type<0 || type>7)
-	{
-		printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type);
-		return;
-	}
-	if (plan<0 || plan>15)
-	{
-		printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan);
-		return;
-	}
-	if (present > 3)
-	{
-		printf("%s: ERROR: present(%d) is out of range.\n", __FUNCTION__, present);
-		return;
-	}
-	if (present >= 0) if (screen<0 || screen>3)
-	{
-		printf("%s: ERROR: screen(%d) is out of range.\n", __FUNCTION__, screen);
-		return;
-	}
-	if (reason > 0x0f)
-	{
-		printf("%s: ERROR: reason(%d) is out of range.\n", __FUNCTION__, reason);
-		return;
-	}
-
-	if (MISDN_IE_DEBG) printf("    type=%d plan=%d present=%d screen=%d readon=%d number='%s'\n", type, plan, present, screen, reason, number);
-
-	l = 1;
-	if (number)
-		l += strlen((char *)number);
-	if (present >= 0)
-	{
-		l += 1;
-		if (reason >= 0)
-			l += 1;
-	}
-	p = msg_put(msg, l+2);
-	if (nt)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(redirect_nr) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_REDIR_NR;
-	p[1] = l;
-	if (present >= 0)
-	{
-		if (reason >= 0)
-		{
-			p[2] = 0x00 + (type<<4) + plan;
-			p[3] = 0x00 + (present<<5) + screen;
-			p[4] = 0x80 + reason;
-			if (number)
-				strncpy((char *)p+5, (char *)number, strlen((char *)number));
-		} else
-		{
-			p[2] = 0x00 + (type<<4) + plan;
-			p[3] = 0x80 + (present<<5) + screen;
-			if (number)
-				strncpy((char *)p+4, (char *)number, strlen((char *)number));
-		}
-	} else
-	{
-		p[2] = 0x80 + (type<<4) + plan;
-		if (number) if (number[0])
-			strncpy((char *)p+3, (char *)number, strlen((char *)number));
-	}
-}
-
-static void dec_ie_redir_nr(unsigned char *p, Q931_info_t *qi, int *type, int *plan, int *present, int *screen, int *reason, char *number, size_t number_len, int nt, struct misdn_bchannel *bc)
-{
-	*type = -1;
-	*plan = -1;
-	*present = -1;
-	*screen = -1;
-	*reason = -1;
-	*number = '\0';
-
-	if (!nt)
-	{
-		p = NULL;
-		if (qi->QI_ELEMENT(redirect_nr))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(redirect_nr) + 1;
-	}
-	if (!p)
-		return;
-	if (p[0] < 1)
-	{
-		printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-		return;
-	}
-
-	*type = (p[1]&0x70) >> 4;
-	*plan = p[1] & 0xf;
-	if (!(p[1] & 0x80))
-	{
-		*present = (p[2]&0x60) >> 5;
-		*screen = p[2] & 0x3;
-		if (!(p[2] & 0x80))
-		{
-			*reason = p[3] & 0x0f;
-			strnncpy(number, (char *)p+4, p[0]-3, number_len);
-		} else
-		{
-			strnncpy(number, (char *)p+3, p[0]-2, number_len);
-		}
-	} else
-	{
-		strnncpy(number, (char *)p+2, p[0]-1, number_len);
-	}
-
-	if (MISDN_IE_DEBG) printf("    type=%d plan=%d present=%d screen=%d reason=%d number='%s'\n", *type, *plan, *present, *screen, *reason, number);
-}
-
-
-/* IE_REDIR_DN (redirection = during MT_NOTIFY) */
-static void enc_ie_redir_dn(unsigned char **ntmode, msg_t *msg, int type, int plan, int present, char *number, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-
-	if (type<0 || type>7)
-	{
-		printf("%s: ERROR: type(%d) is out of range.\n", __FUNCTION__, type);
-		return;
-	}
-	if (plan<0 || plan>15)
-	{
-		printf("%s: ERROR: plan(%d) is out of range.\n", __FUNCTION__, plan);
-		return;
-	}
-	if (present > 3)
-	{
-		printf("%s: ERROR: present(%d) is out of range.\n", __FUNCTION__, present);
-		return;
-	}
-
-	if (MISDN_IE_DEBG) printf("    type=%d plan=%d present=%d number='%s'\n", type, plan, present, number);
-
-	l = 1;
-	if (number)
-		l += strlen((char *)number);
-	if (present >= 0)
-		l += 1;
-	p = msg_put(msg, l+2);
-	if (nt)
-		*ntmode = p+1;
-	else {
-		qi->QI_ELEMENT(redirect_dn) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	}
-	p[0] = IE_REDIR_DN;
-	p[1] = l;
-	if (present >= 0)
-	{
-		p[2] = 0x00 + (type<<4) + plan;
-		p[3] = 0x80 + (present<<5);
-		if (number)
-			strncpy((char *)p+4, (char *)number, strlen((char *)number));
-	} else
-	{
-		p[2] = 0x80 + (type<<4) + plan;
-		if (number)
-			strncpy((char *)p+3, (char *)number, strlen((char *)number));
-	}
-}
-
-static void dec_ie_redir_dn(unsigned char *p, Q931_info_t *qi, int *type, int *plan, int *present, char *number, size_t number_len, int nt, struct misdn_bchannel *bc)
-{
-	*type = -1;
-	*plan = -1;
-	*present = -1;
-	*number = '\0';
-
-	if (!nt)
-	{
-		p = NULL;
-		if (qi->QI_ELEMENT(redirect_dn))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(redirect_dn) + 1;
-	}
-	if (!p)
-		return;
-	if (p[0] < 1)
-	{
-		printf("%s: ERROR: IE too short (%d).\n", __FUNCTION__, p[0]);
-		return;
-	}
-
-	*type = (p[1]&0x70) >> 4;
-	*plan = p[1] & 0xf;
-	if (!(p[1] & 0x80))
-	{
-		*present = (p[2]&0x60) >> 5;
-		strnncpy(number, (char *)p+3, p[0]-2, number_len);
-	} else
-	{
-		strnncpy(number, (char *)p+2, p[0]-1, number_len);
-	}
-
-	if (MISDN_IE_DEBG) printf("    type=%d plan=%d present=%d number='%s'\n", *type, *plan, *present, number);
-}
-
-
-/* IE_USERUSER */
-#if 1
-static void enc_ie_useruser(unsigned char **ntmode, msg_t *msg, int protocol, char *user, int user_len, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	int l;
-
-	if (protocol<0 || protocol>127)
-	{
-		printf("%s: ERROR: protocol(%d) is out of range.\n", __FUNCTION__, protocol);
-		return;
-	}
-	if (!user || user_len<=0)
-	{
-		return;
-	}
-
-	if (MISDN_IE_DEBG) {
-		size_t i;
-		char debug[768];
-
-		for (i = 0; i < user_len; ++i) {
-			sprintf(debug + (i * 3), " %02hhx", (unsigned char)user[i]);
-		}
-		debug[i * 3] = 0;
-		printf("    protocol=%d user-user%s\n", protocol, debug);
-	}
-
-	l = user_len+1;
-	p = msg_put(msg, l+3);
-	if (nt)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(useruser) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_USER_USER;
-	p[1] = l;
-	p[2] = protocol;
-	memcpy(p+3, user, user_len);
-}
-#endif
-
-#if 1
-static void dec_ie_useruser(unsigned char *p, Q931_info_t *qi, int *protocol, char *user, int *user_len, int nt, struct misdn_bchannel *bc)
-{
-	*user_len = 0;
-	*protocol = -1;
-
-	if (!nt)
-	{
-		p = NULL;
-		if (qi->QI_ELEMENT(useruser))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(useruser) + 1;
-	}
-	if (!p)
-		return;
-
-	*user_len = p[0]-1;
-	if (p[0] < 1)
-		return;
-	*protocol = p[1];
-	memcpy(user, p+2, (*user_len<=128)?*(user_len):128); /* clip to 128 maximum */
-
-	if (MISDN_IE_DEBG) {
-		int i;
-		char debug[768];
-
-		for (i = 0; i < *user_len; ++i) {
-			sprintf(debug + (i * 3), " %02hhx", (unsigned char)user[i]);
-		}
-		debug[i * 3] = 0;
-		printf("    protocol=%d user-user%s\n", *protocol, debug);
-	}
-}
-#endif
-
-/* IE_DISPLAY */
-static void enc_ie_restart_ind(unsigned char **ntmode, msg_t *msg, unsigned char rind, int nt, struct misdn_bchannel *bc)
-{
-	unsigned char *p;
-	Q931_info_t *qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-	/* if (MISDN_IE_DEBG) printf("    display='%s' (len=%d)\n", display, strlen((char *)display)); */
-
-	p = msg_put(msg, 3);
-	if (nt)
-		*ntmode = p+1;
-	else
-		qi->QI_ELEMENT(restart_ind) = p - (unsigned char *)qi - sizeof(Q931_info_t);
-	p[0] = IE_RESTART_IND;
-	p[1] = 1;
-	p[2] = rind;
-
-}
diff --git a/channels/misdn/isdn_lib.c b/channels/misdn/isdn_lib.c
deleted file mode 100644
index 80388000a84949dda74ef7c3cdcfa506f0bd9832..0000000000000000000000000000000000000000
--- a/channels/misdn/isdn_lib.c
+++ /dev/null
@@ -1,4815 +0,0 @@
-/*
- * Chan_Misdn -- Channel Driver for Asterisk
- *
- * Interface to mISDN
- *
- * Copyright (C) 2004, Christian Richter
- *
- * Christian Richter <crich@beronet.com>
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License
- */
-
-/*! \file
- * \brief Interface to mISDN
- * \author Christian Richter <crich@beronet.com>
- */
-
-/*** MODULEINFO
-	<support_level>extended</support_level>
- ***/
-
-#include <syslog.h>
-#include <sys/time.h>
-#include <mISDNuser/isdn_debug.h>
-
-#include "isdn_lib_intern.h"
-#include "isdn_lib.h"
-
-enum event_response_e (*cb_event)(enum event_e event, struct misdn_bchannel *bc, void *user_data);
-
-void (*cb_log)(int level, int port, char *tmpl, ...)
-	__attribute__ ((format (printf, 3, 4)));
-
-int (*cb_jb_empty)(struct misdn_bchannel *bc, char *buffer, int len);
-
-
-/*
- * Define ARRAY_LEN() because I cannot
- * #include "asterisk/utils.h"
- */
-#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
-
-#include "asterisk/causes.h"
-
-void misdn_join_conf(struct misdn_bchannel *bc, int conf_id);
-void misdn_split_conf(struct misdn_bchannel *bc, int conf_id);
-
-int misdn_lib_get_l2_up(struct misdn_stack *stack);
-
-struct misdn_stack *get_misdn_stack(void);
-
-int misdn_lib_port_is_pri(int port)
-{
-	struct misdn_stack *stack=get_misdn_stack();
-	for ( ; stack; stack=stack->next) {
-		if (stack->port == port) {
-			return stack->pri;
-		}
-	}
-
-	return -1;
-}
-
-int misdn_lib_port_is_nt(int port)
-{
-	struct misdn_stack *stack=get_misdn_stack();
-	for ( ; stack; stack=stack->next) {
-		if (stack->port == port) {
-			return stack->nt;
-		}
-	}
-
-	return -1;
-}
-
-void misdn_make_dummy(struct misdn_bchannel *dummybc, int port, int l3id, int nt, int channel)
-{
-	memset (dummybc,0,sizeof(struct misdn_bchannel));
-	dummybc->port=port;
-	if (l3id==0)
-		dummybc->l3_id = MISDN_ID_DUMMY;
-	else
-		dummybc->l3_id=l3id;
-
-	dummybc->nt=nt;
-	dummybc->dummy=1;
-	dummybc->channel=channel;
-}
-
-int misdn_lib_port_block(int port)
-{
-	struct misdn_stack *stack=get_misdn_stack();
-	for ( ; stack; stack=stack->next) {
-		if (stack->port == port) {
-			stack->blocked=1;
-			return 0;
-		}
-	}
-	return -1;
-
-}
-
-int misdn_lib_port_unblock(int port)
-{
-	struct misdn_stack *stack=get_misdn_stack();
-	for ( ; stack; stack=stack->next) {
-		if (stack->port == port) {
-			stack->blocked=0;
-			return 0;
-		}
-	}
-	return -1;
-
-}
-
-int misdn_lib_is_port_blocked(int port)
-{
-	struct misdn_stack *stack=get_misdn_stack();
-	for ( ; stack; stack=stack->next) {
-		if (stack->port == port) {
-			return stack->blocked;
-		}
-	}
-	return -1;
-}
-
-int misdn_lib_is_ptp(int port)
-{
-	struct misdn_stack *stack=get_misdn_stack();
-	for ( ; stack; stack=stack->next) {
-		if (stack->port == port) return stack->ptp;
-	}
-	return -1;
-}
-
-int misdn_lib_get_maxchans(int port)
-{
-	struct misdn_stack *stack=get_misdn_stack();
-	for ( ; stack; stack=stack->next) {
-		if (stack->port == port) {
-			if (stack->pri)
-				return 30;
-			else
-				return 2;
-		}
-	}
-	return -1;
-}
-
-
-struct misdn_stack *get_stack_by_bc(struct misdn_bchannel *bc)
-{
-	struct misdn_stack *stack = get_misdn_stack();
-
-	if (!bc)
-		return NULL;
-
-	for ( ; stack; stack = stack->next) {
-		if (bc->port == stack->port)
-			return stack;
-	}
-
-	return NULL;
-}
-
-
-void get_show_stack_details(int port, char *buf)
-{
-	struct misdn_stack *stack = get_misdn_stack();
-
-	for (; stack; stack = stack->next) {
-		if (stack->port == port) {
-			break;
-		}
-	}
-
-	if (stack) {
-		sprintf(buf, "* Port %2d Type %s Prot. %s L2Link %s L1Link:%s Blocked:%d",
-			stack->port,
-			stack->nt ? "NT" : "TE",
-			stack->ptp ? "PTP" : "PMP",
-			(stack->nt && !stack->ptp) ? "UNKN" : stack->l2link ? "UP  " : "DOWN",
-			stack->l1link ? "UP  " : "DOWN",
-			stack->blocked);
-	} else {
-		buf[0] = 0;
-	}
-}
-
-
-static int nt_err_cnt =0 ;
-
-enum global_states {
-	MISDN_INITIALIZING,
-	MISDN_INITIALIZED
-} ;
-
-static enum global_states  global_state=MISDN_INITIALIZING;
-
-
-#include <mISDNuser/net_l2.h>
-#include <mISDNuser/tone.h>
-#include <unistd.h>
-#include <semaphore.h>
-#include <pthread.h>
-#include <signal.h>
-
-#include "isdn_lib.h"
-
-
-struct misdn_lib {
-	/*! \brief mISDN device handle returned by mISDN_open() */
-	int midev;
-
-	pthread_t event_thread;
-	pthread_t event_handler_thread;
-
-	void *user_data;
-
-	msg_queue_t activatequeue;
-
-	sem_t new_msg;
-
-	struct misdn_stack *stack_list;
-} ;
-
-#ifndef ECHOCAN_ON
-#define ECHOCAN_ON 123
-#define ECHOCAN_OFF 124
-#endif
-
-#define MISDN_DEBUG 0
-
-void misdn_tx_jitter(struct misdn_bchannel *bc, int len);
-
-struct misdn_bchannel *find_bc_by_l3id(struct misdn_stack *stack, unsigned long l3id);
-
-int manager_isdn_handler(iframe_t *frm ,msg_t *msg);
-
-int misdn_lib_port_restart(int port);
-int misdn_lib_pid_restart(int pid);
-
-extern struct isdn_msg msgs_g[];
-
-#define ISDN_PID_L3_B_USER 0x430000ff
-#define ISDN_PID_L4_B_USER 0x440000ff
-
-/* #define MISDN_IBUF_SIZE 1024 */
-#define MISDN_IBUF_SIZE 512
-
-/*  Fine Tuning of Inband  Signalling time */
-#define TONE_ALERT_CNT 41 /*  1 Sec  */
-#define TONE_ALERT_SILENCE_CNT 200 /*  4 Sec */
-
-#define TONE_BUSY_CNT 20 /*  ? */
-#define TONE_BUSY_SILENCE_CNT 48 /*  ? */
-
-static int entity;
-
-static struct misdn_lib *glob_mgr;
-
-static char tone_425_flip[TONE_425_SIZE];
-static char tone_silence_flip[TONE_SILENCE_SIZE];
-
-static void misdn_lib_isdn_event_catcher(void *arg);
-static int handle_event_nt(void *dat, void *arg);
-
-
-void stack_holder_add(struct misdn_stack *stack, struct misdn_bchannel *holder);
-void stack_holder_remove(struct misdn_stack *stack, struct misdn_bchannel *holder);
-struct misdn_bchannel *stack_holder_find(struct misdn_stack *stack, unsigned long l3id);
-
-/* from isdn_lib.h */
-	/* user iface */
-void te_lib_destroy(int midev) ;
-struct misdn_bchannel *manager_find_bc_by_pid(int pid);
-void manager_ph_control_block(struct misdn_bchannel *bc, int c1, void *c2, int c2_len);
-void manager_clean_bc(struct misdn_bchannel *bc );
-void manager_bchannel_setup (struct misdn_bchannel *bc);
-void manager_bchannel_cleanup (struct misdn_bchannel *bc);
-
-void ec_chunk( struct misdn_bchannel *bc, unsigned char *rxchunk, unsigned char *txchunk, int chunk_size);
-	/* end */
-int bchdev_echocancel_activate(struct misdn_bchannel* dev);
-void bchdev_echocancel_deactivate(struct misdn_bchannel* dev);
-/* end */
-
-
-static char *bearer2str(int cap) {
-	static char *bearers[]={
-		"Speech",
-		"Audio 3.1k",
-		"Unres Digital",
-		"Res Digital",
-		"Unknown Bearer"
-	};
-
-	switch (cap) {
-	case INFO_CAPABILITY_SPEECH:
-		return bearers[0];
-		break;
-	case INFO_CAPABILITY_AUDIO_3_1K:
-		return bearers[1];
-		break;
-	case INFO_CAPABILITY_DIGITAL_UNRESTRICTED:
-		return bearers[2];
-		break;
-	case INFO_CAPABILITY_DIGITAL_RESTRICTED:
-		return bearers[3];
-		break;
-	default:
-		return bearers[4];
-		break;
-	}
-}
-
-
-static char flip_table[256];
-
-static void init_flip_bits(void)
-{
-	int i,k;
-
-	for (i = 0 ; i < 256 ; i++) {
-		unsigned char sample = 0 ;
-		for (k = 0; k<8; k++) {
-			if ( i & 1 << k ) sample |= 0x80 >>  k;
-		}
-		flip_table[i] = sample;
-	}
-}
-
-static char * flip_buf_bits ( char * buf , int len)
-{
-	int i;
-	char * start = buf;
-
-	for (i = 0 ; i < len; i++) {
-		buf[i] = flip_table[(unsigned char)buf[i]];
-	}
-
-	return start;
-}
-
-
-
-
-static msg_t *create_l2msg(int prim, int dinfo, int size) /* NT only */
-{
-	int i = 0;
-	msg_t *dmsg;
-
-	while(i < 10)
-	{
-		dmsg = prep_l3data_msg(prim, dinfo, size, 256, NULL);
-		if (dmsg)
-			return(dmsg);
-
-		if (!i)
-			printf("cannot allocate memory, trying again...\n");
-		i++;
-		usleep(300000);
-	}
-	printf("cannot allocate memory, system overloaded.\n");
-	exit(-1);
-}
-
-
-
-msg_t *create_l3msg(int prim, int mt, int dinfo, int size, int ntmode)
-{
-	int i = 0;
-	msg_t *dmsg;
-	Q931_info_t *qi;
-	iframe_t *frm;
-
-	if (!ntmode)
-		size = sizeof(Q931_info_t)+2;
-
-	while(i < 10) {
-		if (ntmode) {
-			dmsg = prep_l3data_msg(prim, dinfo, size, 256, NULL);
-			if (dmsg) {
-				return(dmsg);
-			}
-		} else {
-			dmsg = alloc_msg(size+256+mISDN_HEADER_LEN+DEFAULT_HEADROOM);
-			if (dmsg)
-			{
-				memset(msg_put(dmsg,size+mISDN_HEADER_LEN), 0, size+mISDN_HEADER_LEN);
-				frm = (iframe_t *)dmsg->data;
-				frm->prim = prim;
-				frm->dinfo = dinfo;
-				qi = (Q931_info_t *)(dmsg->data + mISDN_HEADER_LEN);
-				qi->type = mt;
-				return(dmsg);
-			}
-		}
-
-		if (!i) printf("cannot allocate memory, trying again...\n");
-		i++;
-		usleep(300000);
-	}
-	printf("cannot allocate memory, system overloaded.\n");
-	exit(-1);
-}
-
-
-static int send_msg (int midev, struct misdn_bchannel *bc, msg_t *dmsg)
-{
-	iframe_t *frm = (iframe_t *)dmsg->data;
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-
-	if (!stack) {
-		cb_log(0,bc->port,"send_msg: IEK!! no stack\n ");
-		return -1;
-	}
-
-	frm->addr = (stack->upper_id | FLG_MSG_DOWN);
-	frm->dinfo = bc->l3_id;
-	frm->len = (dmsg->len) - mISDN_HEADER_LEN;
-
-	cb_log(4,stack->port,"Sending msg, prim:%x addr:%x dinfo:%x\n",frm->prim,frm->addr,frm->dinfo);
-
-	mISDN_write(midev, dmsg->data, dmsg->len, TIMEOUT_1SEC);
-	free_msg(dmsg);
-
-	return 0;
-}
-
-
-static int mypid=1;
-
-
-int misdn_cap_is_speech(int cap)
-/** Poor mans version **/
-{
-	if ( (cap != INFO_CAPABILITY_DIGITAL_UNRESTRICTED) &&
-	     (cap != INFO_CAPABILITY_DIGITAL_RESTRICTED) ) return 1;
-	return 0;
-}
-
-int misdn_inband_avail(struct misdn_bchannel *bc)
-{
-
-	if (!bc->early_bconnect) {
-		/* We have opted to never receive any available inband recorded messages */
-		return 0;
-	}
-
-	switch (bc->progress_indicator) {
-	case INFO_PI_INBAND_AVAILABLE:
-	case INFO_PI_CALL_NOT_E2E_ISDN:
-	case INFO_PI_CALLED_NOT_ISDN:
-		return 1;
-	default:
-		return 0;
-	}
-	return 0;
-}
-
-
-static void dump_chan_list(struct misdn_stack *stack)
-{
-	int i;
-
-	for (i = 0; i <= stack->b_num; ++i) {
-		cb_log(6, stack->port, "Idx:%d stack->cchan:%d in_use:%d Chan:%d\n",
-			i, stack->channels[i], stack->bc[i].in_use, i + 1);
-	}
-#if defined(AST_MISDN_ENHANCEMENTS)
-	for (i = MAX_BCHANS + 1; i < ARRAY_LEN(stack->bc); ++i) {
-		if (stack->bc[i].in_use) {
-			cb_log(6, stack->port, "Idx:%d stack->cchan:%d REGISTER Chan:%d in_use\n",
-				i, stack->channels[i], i + 1);
-		}
-	}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-}
-
-
-void misdn_dump_chanlist(void)
-{
-	struct misdn_stack *stack=get_misdn_stack();
-	for ( ; stack; stack=stack->next) {
-		dump_chan_list(stack);
-	}
-
-}
-
-static int set_chan_in_stack(struct misdn_stack *stack, int channel)
-{
-	cb_log(4,stack->port,"set_chan_in_stack: %d\n",channel);
-	dump_chan_list(stack);
-	if (1 <= channel && channel <= ARRAY_LEN(stack->channels)) {
-		if (!stack->channels[channel-1])
-			stack->channels[channel-1] = 1;
-		else {
-			cb_log(4,stack->port,"channel already in use:%d\n", channel );
-			return -1;
-		}
-	} else {
-		cb_log(0,stack->port,"couldn't set channel %d in\n", channel );
-		return -1;
-	}
-
-	return 0;
-}
-
-
-
-static int find_free_chan_in_stack(struct misdn_stack *stack, struct misdn_bchannel *bc, int channel, int dec)
-{
-	int i;
-	int chan = 0;
-	int bnums;
-
-	if (bc->channel_found) {
-		return 0;
-	}
-
-	bc->channel_found = 1;
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	if (bc->is_register_pool) {
-		pthread_mutex_lock(&stack->st_lock);
-		for (i = MAX_BCHANS + 1; i < ARRAY_LEN(stack->channels); ++i) {
-			if (!stack->channels[i]) {
-				chan = i + 1;
-				cb_log(3, stack->port, " --> found REGISTER chan: %d\n", chan);
-				break;
-			}
-		}
-	} else
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	{
-		cb_log(5, stack->port, "find_free_chan: req_chan:%d\n", channel);
-
-		if (channel < 0 || channel > MAX_BCHANS) {
-			cb_log(0, stack->port, " !! out of bound call to find_free_chan_in_stack! (ch:%d)\n", channel);
-			return 0;
-		}
-
-		--channel;
-
-		pthread_mutex_lock(&stack->st_lock);
-		bnums = stack->pri ? stack->b_num : stack->b_num - 1;
-		if (dec) {
-			for (i = bnums; i >= 0; --i) {
-				if (i != 15 && (channel < 0 || i == channel)) { /* skip E1 D channel ;) and work with chan preselection */
-					if (!stack->channels[i]) {
-						chan = i + 1;
-						cb_log(3, stack->port, " --> found chan%s: %d\n", channel >= 0 ? " (preselected)" : "", chan);
-						break;
-					}
-				}
-			}
-		} else {
-			for (i = 0; i <= bnums; ++i) {
-				if (i != 15 && (channel < 0 || i == channel)) { /* skip E1 D channel ;) and work with chan preselection */
-					if (!stack->channels[i]) {
-						chan = i + 1;
-						cb_log(3, stack->port, " --> found chan%s: %d\n", channel >= 0 ? " (preselected)" : "", chan);
-						break;
-					}
-				}
-			}
-		}
-	}
-
-	if (!chan) {
-		pthread_mutex_unlock(&stack->st_lock);
-		cb_log(1, stack->port, " !! NO FREE CHAN IN STACK\n");
-		dump_chan_list(stack);
-		bc->out_cause = AST_CAUSE_NORMAL_CIRCUIT_CONGESTION;
-		return -1;
-	}
-
-	if (set_chan_in_stack(stack, chan) < 0) {
-		pthread_mutex_unlock(&stack->st_lock);
-		cb_log(0, stack->port, "Channel Already in use:%d\n", chan);
-		bc->out_cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
-		return -1;
-	}
-	pthread_mutex_unlock(&stack->st_lock);
-
-	bc->channel = chan;
-	return 0;
-}
-
-/*!
- * \internal
- * \brief Release a B channel to the allocation pool.
- *
- * \param stack Which port stack B channel belongs.
- * \param channel B channel to release. (Range 1-MAX_BCHANS representing B1-Bn)
- *
- * \note
- * Must be called after clean_up_bc() to make sure that the media stream is
- * no longer connected.
- */
-static void empty_chan_in_stack(struct misdn_stack *stack, int channel)
-{
-	if (channel < 1 || ARRAY_LEN(stack->channels) < channel) {
-		cb_log(0, stack->port, "empty_chan_in_stack: cannot empty channel %d\n", channel);
-		return;
-	}
-
-	cb_log(4, stack->port, "empty_chan_in_stack: %d\n", channel);
-	stack->channels[channel - 1] = 0;
-	dump_chan_list(stack);
-}
-
-char *bc_state2str(enum bchannel_state state) {
-	int i;
-
-	struct bchan_state_s {
-		char *n;
-		enum bchannel_state s;
-	} states[] = {
-		{"BCHAN_CLEANED", BCHAN_CLEANED },
-		{"BCHAN_EMPTY", BCHAN_EMPTY},
-		{"BCHAN_ACTIVATED", BCHAN_ACTIVATED},
-		{"BCHAN_BRIDGED", BCHAN_BRIDGED},
-		{"BCHAN_RELEASE", BCHAN_RELEASE},
-		{"BCHAN_ERROR", BCHAN_ERROR}
-	};
-
-	for (i=0; i< sizeof(states)/sizeof(struct bchan_state_s); i++)
-		if ( states[i].s == state)
-			return states[i].n;
-
-	return "UNKNOWN";
-}
-
-void bc_state_change(struct misdn_bchannel *bc, enum bchannel_state state)
-{
-	cb_log(5,bc->port,"BC_STATE_CHANGE: l3id:%x from:%s to:%s\n",
-		bc->l3_id,
-	       bc_state2str(bc->bc_state),
-	       bc_state2str(state) );
-
-	switch (state) {
-		case BCHAN_ACTIVATED:
-			if (bc->next_bc_state ==  BCHAN_BRIDGED) {
-				misdn_join_conf(bc, bc->conf_id);
-				bc->next_bc_state = BCHAN_EMPTY;
-				return;
-			}
-		default:
-			bc->bc_state=state;
-			break;
-	}
-}
-
-static void bc_next_state_change(struct misdn_bchannel *bc, enum bchannel_state state)
-{
-	cb_log(5,bc->port,"BC_NEXT_STATE_CHANGE: from:%s to:%s\n",
-	       bc_state2str(bc->next_bc_state),
-	       bc_state2str(state) );
-
-	bc->next_bc_state=state;
-}
-
-/*!
- * \internal
- * \brief Empty the B channel record of most call data.
- *
- * \param bc B channel record to empty of most call data.
- *
- * \note
- * Sets the last_used time and must be called before clearing bc->in_use.
- */
-static void empty_bc(struct misdn_bchannel *bc)
-{
-	bc->caller.presentation = 0;	/* allowed */
-	bc->caller.number_plan = NUMPLAN_ISDN;
-	bc->caller.number_type = NUMTYPE_UNKNOWN;
-	bc->caller.name[0] = 0;
-	bc->caller.number[0] = 0;
-	bc->caller.subaddress[0] = 0;
-
-	bc->connected.presentation = 0;	/* allowed */
-	bc->connected.number_plan = NUMPLAN_ISDN;
-	bc->connected.number_type = NUMTYPE_UNKNOWN;
-	bc->connected.name[0] = 0;
-	bc->connected.number[0] = 0;
-	bc->connected.subaddress[0] = 0;
-
-	bc->redirecting.from.presentation = 0;	/* allowed */
-	bc->redirecting.from.number_plan = NUMPLAN_ISDN;
-	bc->redirecting.from.number_type = NUMTYPE_UNKNOWN;
-	bc->redirecting.from.name[0] = 0;
-	bc->redirecting.from.number[0] = 0;
-	bc->redirecting.from.subaddress[0] = 0;
-
-	bc->redirecting.to.presentation = 0;	/* allowed */
-	bc->redirecting.to.number_plan = NUMPLAN_ISDN;
-	bc->redirecting.to.number_type = NUMTYPE_UNKNOWN;
-	bc->redirecting.to.name[0] = 0;
-	bc->redirecting.to.number[0] = 0;
-	bc->redirecting.to.subaddress[0] = 0;
-
-	bc->redirecting.reason = mISDN_REDIRECTING_REASON_UNKNOWN;
-	bc->redirecting.count = 0;
-	bc->redirecting.to_changed = 0;
-
-	bc->dummy=0;
-
-	bc->bframe_len=0;
-
-	bc->cw= 0;
-
-	bc->dec=0;
-	bc->channel = 0;
-
-	bc->sending_complete = 0;
-
-	bc->restart_channel=0;
-
-	bc->conf_id = 0;
-
-	bc->need_more_infos = 0;
-
-	bc->send_dtmf=0;
-	bc->nodsp=0;
-	bc->nojitter=0;
-
-	bc->time_usec=0;
-
-	bc->rxgain=0;
-	bc->txgain=0;
-
-	bc->crypt=0;
-	bc->curptx=0; bc->curprx=0;
-
-	bc->crypt_key[0] = 0;
-
-	bc->generate_tone=0;
-	bc->tone_cnt=0;
-
-	bc->active = 0;
-
-	bc->early_bconnect = 1;
-
-#ifdef MISDN_1_2
-	*bc->pipeline = 0;
-#else
-	bc->ec_enable = 0;
-	bc->ec_deftaps = 128;
-#endif
-
-	bc->AOCD_need_export = 0;
-
-	bc->orig=0;
-
-	bc->cause = AST_CAUSE_NORMAL_CLEARING;
-	bc->out_cause = AST_CAUSE_NORMAL_CLEARING;
-
-	bc->display_connected = 0;	/* none */
-	bc->display_setup = 0;	/* none */
-
-	bc->outgoing_colp = 0;/* pass */
-
-	bc->presentation = 0;	/* allowed */
-	bc->set_presentation = 0;
-
-	bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID;
-
-	bc->progress_coding=0;
-	bc->progress_location=0;
-	bc->progress_indicator=0;
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	bc->div_leg_3_rx_wanted = 0;
-	bc->div_leg_3_tx_pending = 0;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-/** Set Default Bearer Caps **/
-	bc->capability=INFO_CAPABILITY_SPEECH;
-	bc->law=INFO_CODEC_ALAW;
-	bc->mode=0;
-	bc->rate=0x10;
-	bc->user1=0;
-	bc->urate=0;
-
-	bc->hdlc=0;
-
-	bc->dialed.number_plan = NUMPLAN_ISDN;
-	bc->dialed.number_type = NUMTYPE_UNKNOWN;
-	bc->dialed.number[0] = 0;
-	bc->dialed.subaddress[0] = 0;
-
-	bc->info_dad[0] = 0;
-	bc->display[0] = 0;
-	bc->infos_pending[0] = 0;
-	bc->uu[0]=0;
-	bc->uulen=0;
-
-	bc->fac_in.Function = Fac_None;
-	bc->fac_out.Function = Fac_None;
-
-	bc->te_choose_channel = 0;
-	bc->channel_found= 0;
-
-	gettimeofday(&bc->last_used, NULL);
-}
-
-
-static int clean_up_bc(struct misdn_bchannel *bc)
-{
-	int ret=0;
-	unsigned char buff[32];
-	struct misdn_stack * stack;
-
-	cb_log(3, bc->port, "$$$ CLEANUP CALLED pid:%d\n", bc->pid);
-
-	stack=get_stack_by_bc(bc);
-
-	if (!stack) return -1;
-
-	switch (bc->bc_state ) {
-	case BCHAN_CLEANED:
-		cb_log(5, stack->port, "$$$ Already cleaned up bc with stid :%x\n", bc->b_stid);
-		return -1;
-
-	default:
-		break;
-	}
-
-	cb_log(2, stack->port, "$$$ Cleaning up bc with stid :%x pid:%d\n", bc->b_stid, bc->pid);
-
-	manager_ec_disable(bc);
-
-	manager_bchannel_deactivate(bc);
-
-	mISDN_write_frame(stack->midev, buff, bc->layer_id|FLG_MSG_TARGET|FLG_MSG_DOWN, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
-
-	bc->b_stid = 0;
-	bc_state_change(bc, BCHAN_CLEANED);
-
-	return ret;
-}
-
-
-
-static void clear_l3(struct misdn_stack *stack)
-{
-	int i;
-
-	if (global_state == MISDN_INITIALIZED) {
-		for (i = 0; i <= stack->b_num; ++i) {
-			cb_event(EVENT_CLEANUP, &stack->bc[i], NULL);
-			empty_bc(&stack->bc[i]);
-			clean_up_bc(&stack->bc[i]);
-			empty_chan_in_stack(stack, i + 1);
-			stack->bc[i].in_use = 0;
-		}
-#if defined(AST_MISDN_ENHANCEMENTS)
-		for (i = MAX_BCHANS + 1; i < ARRAY_LEN(stack->bc); ++i) {
-			empty_bc(&stack->bc[i]);
-			empty_chan_in_stack(stack, i + 1);
-			stack->bc[i].in_use = 0;
-		}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	}
-}
-
-static int new_te_id = 0;
-
-static int misdn_lib_get_l1_down(struct misdn_stack *stack)
-{
-	/* Pull Up L1 */
-	iframe_t act;
-	act.prim = PH_DEACTIVATE | REQUEST;
-	act.addr = stack->upper_id | FLG_MSG_DOWN;
-	act.dinfo = 0;
-	act.len = 0;
-
-	cb_log(1, stack->port, "SENDING PH_DEACTIVATE | REQ\n");
-	return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC);
-}
-
-
-static int misdn_lib_get_l2_down(struct misdn_stack *stack)
-{
-
-	if (stack->ptp && stack->nt) {
-		msg_t *dmsg;
-		/* L2 */
-		dmsg = create_l2msg(DL_RELEASE| REQUEST, 0, 0);
-
-		pthread_mutex_lock(&stack->nstlock);
-		if (stack->nst.manager_l3(&stack->nst, dmsg))
-			free_msg(dmsg);
-		pthread_mutex_unlock(&stack->nstlock);
-	} else if (!stack->nt) {
-		iframe_t act;
-
-		act.prim = DL_RELEASE| REQUEST;
-		act.addr = (stack->upper_id |FLG_MSG_DOWN)  ;
-
-		act.dinfo = 0;
-		act.len = 0;
-		return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC);
-	}
-	/* cannot deestablish L2 for NT PTMP to unknown TE TEIs */
-
-	return 0;
-}
-
-
-static int misdn_lib_get_l1_up(struct misdn_stack *stack)
-{
-	/* Pull Up L1 */
-	iframe_t act;
-	act.prim = PH_ACTIVATE | REQUEST;
-	act.addr = stack->upper_id | FLG_MSG_DOWN;
-
-	act.dinfo = 0;
-	act.len = 0;
-
-	cb_log(1, stack->port, "SENDING PH_ACTIVATE | REQ\n");
-	return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC);
-}
-
-int misdn_lib_get_l2_up(struct misdn_stack *stack)
-{
-
-	if (stack->ptp && stack->nt) {
-		msg_t *dmsg;
-		/* L2 */
-		dmsg = create_l2msg(DL_ESTABLISH | REQUEST, 0, 0);
-
-		pthread_mutex_lock(&stack->nstlock);
-		if (stack->nst.manager_l3(&stack->nst, dmsg))
-			free_msg(dmsg);
-		pthread_mutex_unlock(&stack->nstlock);
-	} else if (!stack->nt) {
-		iframe_t act;
-
-		act.prim = DL_ESTABLISH | REQUEST;
-		act.addr = (stack->upper_id |FLG_MSG_DOWN)  ;
-
-		act.dinfo = 0;
-		act.len = 0;
-		return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC);
-	}
-	/* cannot establish L2 for NT PTMP to unknown TE TEIs */
-
-	return 0;
-}
-
-static int misdn_lib_get_short_status(struct misdn_stack *stack)
-{
-	iframe_t act;
-
-
-	act.prim = MGR_SHORTSTATUS | REQUEST;
-
-	act.addr = (stack->upper_id | MSG_BROADCAST)  ;
-
-	act.dinfo = SSTATUS_BROADCAST_BIT | SSTATUS_ALL;
-
-	act.len = 0;
-	return mISDN_write(stack->midev, &act, mISDN_HEADER_LEN+act.len, TIMEOUT_1SEC);
-}
-
-
-
-static int create_process(int midev, struct misdn_bchannel *bc)
-{
-	iframe_t ncr;
-	int l3_id;
-	int proc_id;
-	struct misdn_stack *stack;
-
-	stack = get_stack_by_bc(bc);
-	if (stack->nt) {
-		if (find_free_chan_in_stack(stack, bc, bc->channel_preselected ? bc->channel : 0, 0) < 0) {
-			return -1;
-		}
-		cb_log(4, stack->port, " -->  found channel: %d\n", bc->channel);
-
-		for (proc_id = 0; proc_id < MAXPROCS; ++proc_id) {
-			if (stack->procids[proc_id] == 0) {
-				break;
-			}
-		}
-		if (proc_id == MAXPROCS) {
-			cb_log(0, stack->port, "Couldn't Create New ProcId.\n");
-			return -1;
-		}
-
-		stack->procids[proc_id] = 1;
-
-		l3_id = 0xff00 | proc_id;
-		bc->l3_id = l3_id;
-		cb_log(3, stack->port, " --> new_l3id %x\n", l3_id);
-	} else {
-		if ((stack->pri && stack->ptp) || bc->te_choose_channel) {
-			/* we know exactly which channels are in use */
-			if (find_free_chan_in_stack(stack, bc, bc->channel_preselected ? bc->channel : 0, bc->dec) < 0) {
-				return -1;
-			}
-			cb_log(2, stack->port, " -->  found channel: %d\n", bc->channel);
-		} else {
-			/* other phones could have made a call also on this port (ptmp) */
-			bc->channel = 0xff;
-		}
-
-		/* if we are in te-mode, we need to create a process first */
-		if (++new_te_id > 0xffff) {
-			new_te_id = 0x0001;
-		}
-
-		l3_id = (entity << 16) | new_te_id;
-		bc->l3_id = l3_id;
-		cb_log(3, stack->port, "--> new_l3id %x\n", l3_id);
-
-		/* send message */
-		ncr.prim = CC_NEW_CR | REQUEST;
-		ncr.addr = (stack->upper_id | FLG_MSG_DOWN);
-		ncr.dinfo = l3_id;
-		ncr.len = 0;
-		mISDN_write(midev, &ncr, mISDN_HEADER_LEN + ncr.len, TIMEOUT_1SEC);
-	}
-
-	return l3_id;
-}
-
-
-static int setup_bc(struct misdn_bchannel *bc)
-{
-	unsigned char buff[1025];
-	int midev;
-	int channel;
-	int b_stid;
-	int i;
-	mISDN_pid_t pid;
-	int ret;
-
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-
-	if (!stack) {
-		cb_log(0, bc->port, "setup_bc: NO STACK FOUND!!\n");
-		return -1;
-	}
-
-	midev = stack->midev;
-	channel = bc->channel - 1 - (bc->channel > 16);
-	b_stid = stack->b_stids[channel >= 0 ? channel : 0];
-
-	switch (bc->bc_state) {
-		case BCHAN_CLEANED:
-			break;
-		default:
-			cb_log(4, stack->port, "$$$ bc already setup stid :%x (state:%s)\n", b_stid, bc_state2str(bc->bc_state) );
-			return -1;
-	}
-
-	cb_log(5, stack->port, "$$$ Setting up bc with stid :%x\n", b_stid);
-
-	/*check if the b_stid is already initialized*/
-	for (i=0; i <= stack->b_num; i++) {
-		if (stack->bc[i].b_stid == b_stid) {
-			cb_log(0, bc->port, "setup_bc: b_stid:%x already in use !!!\n", b_stid);
-			return -1;
-		}
-	}
-
-	if (b_stid <= 0) {
-		cb_log(0, stack->port," -- Stid <=0 at the moment in channel:%d\n",channel);
-
-		bc_state_change(bc,BCHAN_ERROR);
-		return 1;
-	}
-
-	bc->b_stid = b_stid;
-
-	{
-		layer_info_t li;
-		memset(&li, 0, sizeof(li));
-
-		li.object_id = -1;
-		li.extentions = 0;
-
-		li.st = bc->b_stid; /*  given idx */
-
-
-#define MISDN_DSP
-#ifndef MISDN_DSP
-		bc->nodsp=1;
-#endif
-		if ( bc->hdlc || bc->nodsp) {
-			cb_log(4, stack->port,"setup_bc: without dsp\n");
-			{
-				int l = sizeof(li.name);
-				strncpy(li.name, "B L3", l);
-				li.name[l-1] = 0;
-			}
-			li.pid.layermask = ISDN_LAYER((3));
-			li.pid.protocol[3] = ISDN_PID_L3_B_USER;
-
-			bc->layer=3;
-		} else {
-			cb_log(4, stack->port,"setup_bc: with dsp\n");
-			{
-				int l = sizeof(li.name);
-				strncpy(li.name, "B L4", l);
-				li.name[l-1] = 0;
-			}
-			li.pid.layermask = ISDN_LAYER((4));
-			li.pid.protocol[4] = ISDN_PID_L4_B_USER;
-
-			bc->layer=4;
-		}
-
-		ret = mISDN_new_layer(midev, &li);
-		if (ret ) {
-			cb_log(0, stack->port,"New Layer Err: %d %s\n",ret,strerror(errno));
-
-			bc_state_change(bc,BCHAN_ERROR);
-			return(-EINVAL);
-		}
-
-		bc->layer_id = li.id;
-	}
-
-	memset(&pid, 0, sizeof(pid));
-
-
-
-	cb_log(4, stack->port," --> Channel is %d\n", bc->channel);
-
-	if (bc->nodsp && !bc->hdlc) {
-		cb_log(2, stack->port," --> TRANSPARENT Mode (no DSP, no HDLC)\n");
-		pid.protocol[1] = ISDN_PID_L1_B_64TRANS;
-		pid.protocol[2] = ISDN_PID_L2_B_TRANS;
-		pid.protocol[3] = ISDN_PID_L3_B_USER;
-		pid.layermask = ISDN_LAYER((1)) | ISDN_LAYER((2)) | ISDN_LAYER((3));
-
-	} else if ( bc->hdlc ) {
-		cb_log(2, stack->port," --> HDLC Mode\n");
-		pid.protocol[1] = ISDN_PID_L1_B_64HDLC ;
-		pid.protocol[2] = ISDN_PID_L2_B_TRANS  ;
-		pid.protocol[3] = ISDN_PID_L3_B_USER;
-		pid.layermask = ISDN_LAYER((1)) | ISDN_LAYER((2)) | ISDN_LAYER((3)) ;
-	} else {
-		cb_log(2, stack->port," --> TRANSPARENT Mode\n");
-		pid.protocol[1] = ISDN_PID_L1_B_64TRANS;
-		pid.protocol[2] = ISDN_PID_L2_B_TRANS;
-		pid.protocol[3] = ISDN_PID_L3_B_DSP;
-		pid.protocol[4] = ISDN_PID_L4_B_USER;
-		pid.layermask = ISDN_LAYER((1)) | ISDN_LAYER((2)) | ISDN_LAYER((3)) | ISDN_LAYER((4));
-
-	}
-
-	ret = mISDN_set_stack(midev, bc->b_stid, &pid);
-
-	if (ret){
-		cb_log(0, stack->port,"$$$ Set Stack Err: %d %s\n",ret,strerror(errno));
-
-		mISDN_write_frame(midev, buff, bc->layer_id, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
-
-		bc_state_change(bc,BCHAN_ERROR);
-		cb_event(EVENT_BCHAN_ERROR, bc, glob_mgr->user_data);
-		return(-EINVAL);
-	}
-
-	ret = mISDN_get_setstack_ind(midev, bc->layer_id);
-
-	if (ret) {
-		cb_log(0, stack->port,"$$$ Set StackIND Err: %d %s\n",ret,strerror(errno));
-		mISDN_write_frame(midev, buff, bc->layer_id, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
-
-		bc_state_change(bc,BCHAN_ERROR);
-		cb_event(EVENT_BCHAN_ERROR, bc, glob_mgr->user_data);
-		return(-EINVAL);
-	}
-
-	ret = mISDN_get_layerid(midev, bc->b_stid, bc->layer) ;
-
-	bc->addr = ret>0? ret : 0;
-
-	if (!bc->addr) {
-		cb_log(0, stack->port,"$$$ Get Layerid Err: %d %s\n",ret,strerror(errno));
-		mISDN_write_frame(midev, buff, bc->layer_id, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
-
-		bc_state_change(bc,BCHAN_ERROR);
-		cb_event(EVENT_BCHAN_ERROR, bc, glob_mgr->user_data);
-		return (-EINVAL);
-	}
-
-	manager_bchannel_activate(bc);
-
-	bc_state_change(bc,BCHAN_ACTIVATED);
-
-	return 0;
-}
-
-
-
-/** IFACE **/
-static int init_bc(struct misdn_stack *stack, struct misdn_bchannel *bc, int midev, int port, int bidx)
-{
-	if (!bc) {
-		return -1;
-	}
-
-	cb_log(8, port, "Init.BC %d.\n",bidx);
-
-	bc->send_lock = malloc(sizeof(struct send_lock)); /* XXX BUG! memory leak never freed */
-	if (!bc->send_lock) {
-		return -1;
-	}
-	pthread_mutex_init(&bc->send_lock->lock, NULL);
-
-	empty_bc(bc);
-
-	bc->port=stack->port;
-	bc_state_change(bc, BCHAN_CLEANED);
-	bc->nt=stack->nt?1:0;
-	bc->pri=stack->pri;
-
-	{
-		ibuffer_t* ibuf= init_ibuffer(MISDN_IBUF_SIZE);
-
-		if (!ibuf) return -1;
-
-		clear_ibuffer( ibuf);
-
-		ibuf->rsem=malloc(sizeof(sem_t));
-		if (!ibuf->rsem) {
-			return -1;
-		}
-
-		bc->astbuf=ibuf;
-
-		if (sem_init(ibuf->rsem,1,0)<0)
-			sem_init(ibuf->rsem,0,0);
-
-	}
-
-#if 0	/* This code does not seem to do anything useful */
-	if (bidx <= stack->b_num) {
-		unsigned char buff[1025];
-		iframe_t *frm = (iframe_t *) buff;
-		stack_info_t *stinf;
-		int ret;
-
-		ret = mISDN_get_stack_info(midev, stack->port, buff, sizeof(buff));
-		if (ret < 0) {
-			cb_log(0, port, "%s: Cannot get stack info for this port. (ret=%d)\n", __FUNCTION__, ret);
-			return -1;
-		}
-
-		stinf = (stack_info_t *)&frm->data.p;
-
-		cb_log(8, port, " --> Child %x\n",stinf->child[bidx]);
-	}
-#endif
-
-	return 0;
-}
-
-
-
-static struct misdn_stack *stack_init(int midev, int port, int ptp)
-{
-	int ret;
-	unsigned char buff[1025];
-	iframe_t *frm = (iframe_t *)buff;
-	stack_info_t *stinf;
-	struct misdn_stack *stack;
-	int i;
-	layer_info_t li;
-
-	stack = calloc(1, sizeof(struct misdn_stack));
-	if (!stack) {
-		return NULL;
-	}
-
-	cb_log(8, port, "Init. Stack.\n");
-
-	stack->port=port;
-	stack->midev=midev;
-	stack->ptp=ptp;
-
-	stack->holding=NULL;
-	stack->pri=0;
-
-	msg_queue_init(&stack->downqueue);
-
-	pthread_mutex_init(&stack->st_lock, NULL);
-
-	/* query port's requirements */
-	ret = mISDN_get_stack_info(midev, port, buff, sizeof(buff));
-	if (ret < 0) {
-		cb_log(0, port, "%s: Cannot get stack info for this port. (ret=%d)\n", __FUNCTION__, ret);
-		free(stack);
-		return(NULL);
-	}
-
-	stinf = (stack_info_t *)&frm->data.p;
-
-	stack->d_stid = stinf->id;
-	stack->b_num = stinf->childcnt;
-
-	for (i=0; i<=stinf->childcnt; i++)
-		stack->b_stids[i] = stinf->child[i];
-
-	switch(stinf->pid.protocol[0] & ~ISDN_PID_FEATURE_MASK) {
-	case ISDN_PID_L0_TE_S0:
-		cb_log(8, port, "TE Stack\n");
-		stack->nt=0;
-		break;
-	case ISDN_PID_L0_NT_S0:
-		cb_log(8, port, "NT Stack\n");
-		stack->nt=1;
-		break;
-	case ISDN_PID_L0_TE_E1:
-		cb_log(8, port, "TE S2M Stack\n");
-		stack->nt=0;
-		stack->pri=1;
-		break;
-	case ISDN_PID_L0_NT_E1:
-		cb_log(8, port, "NT S2M Stack\n");
-		stack->nt=1;
-		stack->pri=1;
-		break;
-	default:
-		cb_log(0, port, "this is a unknown port type 0x%08x\n", stinf->pid.protocol[0]);
-
-	}
-
-	if (!stack->nt) {
-		if (stinf->pid.protocol[2] & ISDN_PID_L2_DF_PTP ) {
-			stack->ptp = 1;
-		} else {
-			stack->ptp = 0;
-		}
-	}
-
-	{
-		int ret;
-		int nt=stack->nt;
-
-		memset(&li, 0, sizeof(li));
-		{
-			int l = sizeof(li.name);
-			strncpy(li.name,nt?"net l2":"user l4", l);
-			li.name[l-1] = 0;
-		}
-		li.object_id = -1;
-		li.extentions = 0;
-		li.pid.protocol[nt?2:4] = nt?ISDN_PID_L2_LAPD_NET:ISDN_PID_L4_CAPI20;
-		li.pid.layermask = ISDN_LAYER((nt?2:4));
-		li.st = stack->d_stid;
-
-
-		ret = mISDN_new_layer(midev, &li);
-		if (ret) {
-			cb_log(0, port, "%s: Cannot add layer %d to this port.\n", __FUNCTION__, nt?2:4);
-			free(stack);
-			return(NULL);
-		}
-
-
-		ret = mISDN_register_layer(midev, stack->d_stid, li.id);
-		if (ret)
-		{
-			cb_log(0,port,"Cannot register layer %d of this port.\n", nt?2:4);
-			free(stack);
-			return(NULL);
-		}
-
-		stack->lower_id = mISDN_get_layerid(midev, stack->d_stid, nt?1:3);
-		if (stack->lower_id < 0) {
-			cb_log(0, port, "%s: Cannot get layer(%d) id of this port.\n", __FUNCTION__, nt?1:3);
-			free(stack);
-			return(NULL);
-		}
-
-		stack->upper_id = mISDN_get_layerid(midev, stack->d_stid, nt?2:4);
-		if (stack->upper_id < 0) {
-			cb_log(0, port, "%s: Cannot get layer(%d) id of this port.\n", __FUNCTION__, nt?2:4);
-			free(stack);
-			return(NULL);
-		}
-
-		/* create nst (nt-mode only) */
-		if (nt) {
-
-			memset(&stack->nst, 0, sizeof(net_stack_t));
-			memset(&stack->mgr, 0, sizeof(manager_t));
-
-			stack->mgr.nst = &stack->nst;
-			stack->nst.manager = &stack->mgr;
-
-			stack->nst.l3_manager = handle_event_nt;
-			stack->nst.device = midev;
-			stack->nst.cardnr = port;
-			stack->nst.d_stid = stack->d_stid;
-
-			stack->nst.feature = FEATURE_NET_HOLD;
-			if (stack->ptp)
-				stack->nst.feature |= FEATURE_NET_PTP;
-			if (stack->pri)
-				stack->nst.feature |= FEATURE_NET_CRLEN2 | FEATURE_NET_EXTCID;
-
-			stack->nst.l1_id = stack->lower_id; /* never used */
-			stack->nst.l2_id = stack->upper_id;
-
-			msg_queue_init(&stack->nst.down_queue);
-			pthread_mutex_init(&stack->nstlock, NULL);
-
-			Isdnl2Init(&stack->nst);
-			Isdnl3Init(&stack->nst);
-
-		}
-
- 		stack->l1link=0;
- 		stack->l2link=0;
-#if 0
-		if (!stack->nt) {
- 			misdn_lib_get_short_status(stack);
- 		} else {
- 			misdn_lib_get_l1_up(stack);
- 			if (!stack->ptp) misdn_lib_get_l1_up(stack);
- 			misdn_lib_get_l2_up(stack);
- 		}
-#endif
-
-		misdn_lib_get_short_status(stack);
-		misdn_lib_get_l1_up(stack);
-		/* handle_l1 will start L2 for NT. */
-		if (!stack->nt) {
-			misdn_lib_get_l2_up(stack);
-		}
-	}
-
-	cb_log(8, port, "stack_init: lowerId:%x upperId:%x\n", stack->lower_id, stack->upper_id);
-
-	return stack;
-}
-
-
-static void stack_destroy(struct misdn_stack *stack)
-{
-	char buf[1024];
-	if (!stack) return;
-
-	if (stack->nt) {
-		pthread_mutex_destroy(&stack->nstlock);
-		cleanup_Isdnl2(&stack->nst);
-		cleanup_Isdnl3(&stack->nst);
-	}
-
-	if (stack->upper_id)
-		mISDN_write_frame(stack->midev, buf, stack->upper_id, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
-
-	pthread_mutex_destroy(&stack->st_lock);
-}
-
-
-static struct misdn_stack * find_stack_by_addr(int  addr)
-{
-	struct misdn_stack *stack;
-
-	for (stack = glob_mgr->stack_list; stack; stack = stack->next) {
-		if ((stack->upper_id & STACK_ID_MASK) == (addr & STACK_ID_MASK)) {
-			/* Found the stack */
-			break;
-		}
-	}
-
-	return stack;
-}
-
-
-static struct misdn_stack *find_stack_by_port(int port)
-{
-	struct misdn_stack *stack;
-
-	for (stack = glob_mgr->stack_list; stack; stack = stack->next) {
-		if (stack->port == port) {
-			/* Found the stack */
-			break;
-		}
-	}
-
-	return stack;
-}
-
-static struct misdn_stack *find_stack_by_mgr(manager_t *mgr_nt)
-{
-	struct misdn_stack *stack;
-
-	for (stack = glob_mgr->stack_list; stack; stack = stack->next) {
-		if (&stack->mgr == mgr_nt) {
-			/* Found the stack */
-			break;
-		}
-	}
-
-	return stack;
-}
-
-static struct misdn_bchannel *find_bc_by_masked_l3id(struct misdn_stack *stack, unsigned long l3id, unsigned long mask)
-{
-	int i;
-
-	for (i = 0; i <= stack->b_num; ++i) {
-		if (stack->bc[i].in_use && (stack->bc[i].l3_id & mask) == (l3id & mask)) {
-			return &stack->bc[i];
-		}
-	}
-#if defined(AST_MISDN_ENHANCEMENTS)
-	/* Search the B channel records for a REGISTER signaling link. */
-	for (i = MAX_BCHANS + 1; i < ARRAY_LEN(stack->bc); ++i) {
-		if (stack->bc[i].in_use && (stack->bc[i].l3_id & mask) == (l3id & mask)) {
-			return &stack->bc[i];
-		}
-	}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	return stack_holder_find(stack, l3id);
-}
-
-
-struct misdn_bchannel *find_bc_by_l3id(struct misdn_stack *stack, unsigned long l3id)
-{
-	int i;
-
-	for (i = 0; i <= stack->b_num; ++i) {
-		if (stack->bc[i].in_use && stack->bc[i].l3_id == l3id) {
-			return &stack->bc[i];
-		}
-	}
-#if defined(AST_MISDN_ENHANCEMENTS)
-	/* Search the B channel records for a REGISTER signaling link. */
-	for (i = MAX_BCHANS + 1; i < ARRAY_LEN(stack->bc); ++i) {
-		if (stack->bc[i].in_use && stack->bc[i].l3_id == l3id) {
-			return &stack->bc[i];
-		}
-	}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	return stack_holder_find(stack, l3id);
-}
-
-static struct misdn_bchannel *find_bc_by_addr(unsigned long addr)
-{
-	struct misdn_stack *stack;
-	int i;
-
-	for (stack = glob_mgr->stack_list; stack; stack = stack->next) {
-		for (i = 0; i <= stack->b_num; i++) {
-			if (stack->bc[i].in_use
-				&& ((stack->bc[i].addr & STACK_ID_MASK) == (addr & STACK_ID_MASK)
-					|| stack->bc[i].layer_id == addr)) {
-				return &stack->bc[i];
-			}
-		}
-	}
-
-	return NULL;
-}
-
-static struct misdn_bchannel *find_bc_by_confid(unsigned long confid)
-{
-	struct misdn_stack *stack;
-	int i;
-
-	for (stack = glob_mgr->stack_list; stack; stack = stack->next) {
-		for (i = 0; i <= stack->b_num; i++) {
-			if (stack->bc[i].in_use && stack->bc[i].conf_id == confid) {
-				return &stack->bc[i];
-			}
-		}
-	}
-	return NULL;
-}
-
-
-static int handle_event_te(struct misdn_bchannel *bc, enum event_e event, iframe_t *frm)
-{
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-
-	switch (event) {
-		case EVENT_CONNECT_ACKNOWLEDGE:
-			setup_bc(bc);
-
-			if ( *bc->crypt_key ) {
-				cb_log(4, stack->port,
-					"ENABLING BLOWFISH channel:%d caller%d:\"%s\" <%s> dialed%d:%s\n",
-					bc->channel,
-					bc->caller.number_type,
-					bc->caller.name,
-					bc->caller.number,
-					bc->dialed.number_type,
-					bc->dialed.number);
-				manager_ph_control_block(bc,  BF_ENABLE_KEY, bc->crypt_key, strlen(bc->crypt_key) );
-			}
-
-			if (misdn_cap_is_speech(bc->capability)) {
-				if (  !bc->nodsp) manager_ph_control(bc,  DTMF_TONE_START, 0);
-				manager_ec_enable(bc);
-
-				if ( bc->txgain != 0 ) {
-					cb_log(4, stack->port, "--> Changing txgain to %d\n", bc->txgain);
-					manager_ph_control(bc, VOL_CHANGE_TX, bc->txgain);
-				}
-				if ( bc->rxgain != 0 ) {
-					cb_log(4, stack->port, "--> Changing rxgain to %d\n", bc->rxgain);
-					manager_ph_control(bc, VOL_CHANGE_RX, bc->rxgain);
-				}
-			}
-
-			break;
-		case EVENT_CONNECT:
-
-			if ( *bc->crypt_key ) {
-				cb_log(4, stack->port,
-					"ENABLING BLOWFISH channel:%d caller%d:\"%s\" <%s> dialed%d:%s\n",
-					bc->channel,
-					bc->caller.number_type,
-					bc->caller.name,
-					bc->caller.number,
-					bc->dialed.number_type,
-					bc->dialed.number);
-				manager_ph_control_block(bc,  BF_ENABLE_KEY, bc->crypt_key, strlen(bc->crypt_key) );
-			}
-		case EVENT_ALERTING:
-		case EVENT_PROGRESS:
-		case EVENT_PROCEEDING:
-		case EVENT_SETUP_ACKNOWLEDGE:
-		case EVENT_SETUP:
-		{
-			if (bc->channel == 0xff || bc->channel<=0)
-				bc->channel=0;
-
-			if (find_free_chan_in_stack(stack, bc, bc->channel, 0)<0){
-				if (!stack->pri && !stack->ptp)  {
-					bc->cw=1;
-					break;
-				}
-
-				if (!bc->channel) {
-					cb_log(0, stack->port, "Any Channel Requested, but we have no more!!\n");
-				} else {
-					cb_log(0, stack->port,
-						"Requested Channel Already in Use releasing this call with cause %d!!!!\n",
-						bc->out_cause);
-				}
-
-				/* when the channel is already in use, we can't
-				 * simply clear it, we need to make sure that
-				 * it will still be marked as in_use in the
-				 * available channels list.*/
-				bc->channel=0;
-
-				misdn_lib_send_event(bc,EVENT_RELEASE_COMPLETE);
-				return -1;
-			}
-
-			if (event != EVENT_SETUP) {
-				setup_bc(bc);
-			}
-			break;
-		}
-
-		case EVENT_RELEASE_COMPLETE:
-		case EVENT_RELEASE:
-			break;
-		default:
-			break;
-	}
-	return 0;
-}
-
-static int handle_cr ( struct misdn_stack *stack, iframe_t *frm)
-{
-	struct misdn_bchannel dummybc;
-	struct misdn_bchannel *bc;
-	int channel;
-
-	if (!stack) return -1;
-
-	switch (frm->prim) {
-	case CC_NEW_CR|INDICATION:
-		cb_log(7, stack->port, " --> lib: NEW_CR Ind with l3id:%x on this port.\n",frm->dinfo);
-
-		bc = misdn_lib_get_free_bc(stack->port, 0, 1, 0);
-		if (!bc) {
-			cb_log(0, stack->port, " --> !! lib: No free channel!\n");
-			return -1;
-		}
-
-		cb_log(7, stack->port, " --> new_process: New L3Id: %x\n",frm->dinfo);
-		bc->l3_id=frm->dinfo;
-		return 1;
-	case CC_NEW_CR|CONFIRM:
-		return 1;
-	case CC_NEW_CR|REQUEST:
-		return 1;
-	case CC_RELEASE_CR|REQUEST:
-		return 1;
-	case CC_RELEASE_CR|CONFIRM:
-		break;
-	case CC_RELEASE_CR|INDICATION:
-		cb_log(4, stack->port, " --> lib: RELEASE_CR Ind with l3id:%x\n", frm->dinfo);
-		bc = find_bc_by_l3id(stack, frm->dinfo);
-		if (!bc) {
-			cb_log(4, stack->port, " --> Didn't find BC so temporarily creating dummy BC (l3id:%x) on this port.\n", frm->dinfo);
-			misdn_make_dummy(&dummybc, stack->port, frm->dinfo, stack->nt, 0);
-			bc = &dummybc;
-		}
-
-		channel = bc->channel;
-		cb_log(4, stack->port, " --> lib: CLEANING UP l3id: %x\n", frm->dinfo);
-
-		/* bc->pid = 0; */
-		bc->need_disconnect = 0;
-		bc->need_release = 0;
-		bc->need_release_complete = 0;
-
-		cb_event(EVENT_CLEANUP, bc, glob_mgr->user_data);
-
-		empty_bc(bc);
-		clean_up_bc(bc);
-
-		if (channel > 0)
-			empty_chan_in_stack(stack, channel);
-		bc->in_use = 0;
-
-		dump_chan_list(stack);
-
-		if (bc->stack_holder) {
-			cb_log(4, stack->port, "REMOVING Holder\n");
-			stack_holder_remove(stack, bc);
-			free(bc);
-		}
-
-		return 1;
-	default:
-		break;
-	}
-
-	return 0;
-}
-
-
-/* Empties bc if it's reserved (no SETUP out yet) */
-void misdn_lib_release(struct misdn_bchannel *bc)
-{
-	int channel;
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-
-	if (!stack) {
-		cb_log(1,0,"misdn_release: No Stack found\n");
-		return;
-	}
-
-	channel = bc->channel;
-	empty_bc(bc);
-	clean_up_bc(bc);
-	if (channel > 0) {
-		empty_chan_in_stack(stack, channel);
-	}
-	bc->in_use=0;
-}
-
-
-
-
-int misdn_lib_get_port_up (int port)
-{ /* Pull Up L1 */
-	struct misdn_stack *stack;
-
-	for (stack=glob_mgr->stack_list;
-	     stack;
-	     stack=stack->next) {
-
-		if (stack->port == port) {
-
-			if (!stack->l1link)
-				misdn_lib_get_l1_up(stack);
-			/* handle_l1 will start L2 for NT. */
-			if (!stack->l2link && !stack->nt) {
-				misdn_lib_get_l2_up(stack);
-			}
-
-			return 0;
-		}
-	}
-	return 0;
-}
-
-
-int misdn_lib_get_port_down (int port)
-{ /* Pull Down L1 */
-	struct misdn_stack *stack;
-	for (stack=glob_mgr->stack_list;
-	     stack;
-	     stack=stack->next) {
-		if (stack->port == port) {
-				if (stack->l2link)
-					misdn_lib_get_l2_down(stack);
-				misdn_lib_get_l1_down(stack);
-			return 0;
-		}
-	}
-	return 0;
-}
-
-int misdn_lib_port_up(int port, int check)
-{
-	struct misdn_stack *stack;
-
-
-	for (stack=glob_mgr->stack_list;
-	     stack;
-	     stack=stack->next) {
-
-		if (stack->port == port) {
-
-			if (stack->blocked) {
-				cb_log(0,port, "Port Blocked:%d L2:%d L1:%d\n", stack->blocked, stack->l2link, stack->l1link);
-				return -1;
-			}
-
-			if (stack->ptp ) {
-
-				if (stack->l1link && stack->l2link) {
-					return 1;
-				} else {
-					cb_log(1,port, "Port Down L2:%d L1:%d\n",
-						stack->l2link, stack->l1link);
-					return 0;
-				}
-			} else {
-				if ( !check || stack->l1link )
-					return 1;
-				else {
-					cb_log(1,port, "Port down PMP\n");
-					return 0;
-				}
-			}
-		}
-	}
-
-	return -1;
-}
-
-
-static int release_cr(struct misdn_stack *stack, mISDNuser_head_t *hh)
-{
-	struct misdn_bchannel *bc=find_bc_by_l3id(stack, hh->dinfo);
-	struct misdn_bchannel dummybc;
-	iframe_t frm; /* fake te frm to remove callref from global callreflist */
-
-	frm.dinfo = hh->dinfo;
-	frm.addr=stack->upper_id | FLG_MSG_DOWN;
-	frm.prim = CC_RELEASE_CR|INDICATION;
-	cb_log(4, stack->port, " --> CC_RELEASE_CR: Faking Release_cr for %x l3id:%x\n",frm.addr, frm.dinfo);
-
-	/** removing procid **/
-	if (!bc) {
-		cb_log(4, stack->port, " --> Didn't find BC so temporarily creating dummy BC (l3id:%x) on this port.\n", hh->dinfo);
-		misdn_make_dummy(&dummybc, stack->port, hh->dinfo, stack->nt, 0);
-		bc=&dummybc;
-	}
-
-	if ((bc->l3_id & 0xff00) == 0xff00) {
-		cb_log(4, stack->port, " --> Removing Process Id:%x on this port.\n", bc->l3_id & 0xff);
-		stack->procids[bc->l3_id & 0xff] = 0;
-	}
-
-	if (handle_cr(stack, &frm)<0) {
-	}
-
-	return 0 ;
-}
-
-static int handle_event_nt(void *dat, void *arg)
-{
-	struct misdn_bchannel dummybc;
-	struct misdn_bchannel *bc;
-	manager_t *mgr = (manager_t *)dat;
-	msg_t *msg = (msg_t *)arg;
-	msg_t *dmsg;
-	mISDNuser_head_t *hh;
-	struct misdn_stack *stack;
-	enum event_e event;
-	int reject=0;
-	int l3id;
-	int channel;
-	int tmpcause;
-
-	if (!msg || !mgr)
-		return(-EINVAL);
-
-	stack = find_stack_by_mgr(mgr);
-	hh=(mISDNuser_head_t*)msg->data;
-
-	/*
-	 * When we are called from the mISDNuser lib, the nstlock is held and it
-	 * must be held when we return.  We unlock here because the lib may be
-	 * entered again recursively.
-	 */
-	pthread_mutex_unlock(&stack->nstlock);
-
-	cb_log(5, stack->port, " --> lib: prim %x dinfo %x\n",hh->prim, hh->dinfo);
-	switch(hh->prim) {
-	case CC_RETRIEVE|INDICATION:
-	{
-		struct misdn_bchannel *hold_bc;
-		iframe_t frm; /* fake te frm to add callref to global callreflist */
-
-		frm.dinfo = hh->dinfo;
-		frm.addr=stack->upper_id | FLG_MSG_DOWN;
-		frm.prim = CC_NEW_CR|INDICATION;
-		if (handle_cr( stack, &frm)< 0) {
-			goto ERR_NO_CHANNEL;
-		}
-
-		bc = find_bc_by_l3id(stack, hh->dinfo);
-		hold_bc = stack_holder_find(stack, bc->l3_id);
-		cb_log(4, stack->port, "bc_l3id:%x holded_bc_l3id:%x\n",bc->l3_id, hold_bc->l3_id);
-
-		if (hold_bc) {
-			cb_log(4, stack->port, "REMOVING Holder\n");
-
-			/* swap the backup to our new channel back */
-			stack_holder_remove(stack, hold_bc);
-			memcpy(bc, hold_bc, sizeof(*bc));
-			free(hold_bc);
-
-			bc->holded=0;
-			bc->b_stid=0;
-		}
-		break;
-	}
-
-	case CC_SETUP | CONFIRM:
-		l3id = *((int *) (msg->data + mISDNUSER_HEAD_SIZE));
-
-		cb_log(4, stack->port, " --> lib: Event_ind:SETUP CONFIRM [NT] : new L3ID is %x\n", l3id);
-
-		bc = find_bc_by_l3id(stack, hh->dinfo);
-		if (bc) {
-			cb_log (2, bc->port, "I IND :CC_SETUP|CONFIRM: old l3id:%x new l3id:%x\n", bc->l3_id, l3id);
-			bc->l3_id = l3id;
-			cb_event(EVENT_NEW_L3ID, bc, glob_mgr->user_data);
-		} else {
-			cb_log(4, stack->port, "Bc Not found (after SETUP CONFIRM)\n");
-		}
-		free_msg(msg);
-		pthread_mutex_lock(&stack->nstlock);
-		return 0;
-
-	case CC_SETUP | INDICATION:
-		bc = misdn_lib_get_free_bc(stack->port, 0, 1, 0);
-		if (!bc) {
-			goto ERR_NO_CHANNEL;
-		}
-
-		cb_log(4, stack->port, " --> new_process: New L3Id: %x\n",hh->dinfo);
-		bc->l3_id=hh->dinfo;
-		break;
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	case CC_REGISTER | CONFIRM:
-		l3id = *((int *) (msg->data + mISDNUSER_HEAD_SIZE));
-
-		cb_log(4, stack->port, " --> lib: Event_ind:REGISTER CONFIRM [NT] : new L3ID is %x\n", l3id);
-
-		bc = find_bc_by_l3id(stack, hh->dinfo);
-		if (bc) {
-			cb_log (2, bc->port, "I IND :CC_REGISTER|CONFIRM: old l3id:%x new l3id:%x\n", bc->l3_id, l3id);
-			bc->l3_id = l3id;
-		} else {
-			cb_log(4, stack->port, "Bc Not found (after REGISTER CONFIRM)\n");
-		}
-		free_msg(msg);
-		pthread_mutex_lock(&stack->nstlock);
-		return 0;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	case CC_REGISTER | INDICATION:
-		bc = misdn_lib_get_register_bc(stack->port);
-		if (!bc) {
-			goto ERR_NO_CHANNEL;
-		}
-
-		cb_log(4, stack->port, " --> new_process: New L3Id: %x\n",hh->dinfo);
-		bc->l3_id=hh->dinfo;
-		break;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-	case CC_CONNECT_ACKNOWLEDGE|INDICATION:
-		break;
-
-	case CC_ALERTING|INDICATION:
-	case CC_PROCEEDING|INDICATION:
-	case CC_SETUP_ACKNOWLEDGE|INDICATION:
-	case CC_CONNECT|INDICATION:
-		break;
-	case CC_DISCONNECT|INDICATION:
-		bc = find_bc_by_l3id(stack, hh->dinfo);
-		if (!bc) {
-			bc=find_bc_by_masked_l3id(stack, hh->dinfo, 0xffff0000);
-			if (bc) {
-				int myprocid=bc->l3_id&0x0000ffff;
-
-				hh->dinfo=(hh->dinfo&0xffff0000)|myprocid;
-				cb_log(3,stack->port,"Reject dinfo: %x cause:%d\n",hh->dinfo,bc->cause);
-				reject=1;
-			}
-		}
-		break;
-
-	case CC_FACILITY|INDICATION:
-		bc = find_bc_by_l3id(stack, hh->dinfo);
-		if (!bc) {
-			bc=find_bc_by_masked_l3id(stack, hh->dinfo, 0xffff0000);
-			if (bc) {
-				int myprocid=bc->l3_id&0x0000ffff;
-
-				hh->dinfo=(hh->dinfo&0xffff0000)|myprocid;
-				cb_log(4,bc->port,"Repaired reject Bug, new dinfo: %x\n",hh->dinfo);
-			}
-		}
-		break;
-
-	case CC_RELEASE_COMPLETE|INDICATION:
-		break;
-
-	case CC_SUSPEND|INDICATION:
-		cb_log(4, stack->port, " --> Got Suspend, sending Reject for now\n");
-		dmsg = create_l3msg(CC_SUSPEND_REJECT | REQUEST,MT_SUSPEND_REJECT, hh->dinfo,sizeof(RELEASE_COMPLETE_t), 1);
-		pthread_mutex_lock(&stack->nstlock);
-		stack->nst.manager_l3(&stack->nst, dmsg);
-		free_msg(msg);
-		return 0;
-
-	case CC_RESUME|INDICATION:
-		break;
-
-	case CC_RELEASE|CONFIRM:
-		bc = find_bc_by_l3id(stack, hh->dinfo);
-		if (bc) {
-			cb_log(1, stack->port, "CC_RELEASE|CONFIRM (l3id:%x), sending RELEASE_COMPLETE\n", hh->dinfo);
-			misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE);
-		}
-		break;
-
-	case CC_RELEASE|INDICATION:
-		break;
-
-	case CC_RELEASE_CR|INDICATION:
-		release_cr(stack, hh);
-		free_msg(msg);
-		pthread_mutex_lock(&stack->nstlock);
-		return 0;
-
-	case CC_NEW_CR|INDICATION:
-		/*  Got New CR for bchan, for now I handle this one in */
-		/*  connect_ack, Need to be changed */
-		l3id = *((int *) (msg->data + mISDNUSER_HEAD_SIZE));
-
-		bc = find_bc_by_l3id(stack, hh->dinfo);
-		if (!bc) {
-			cb_log(0, stack->port, " --> In NEW_CR: didn't found bc ??\n");
-			pthread_mutex_lock(&stack->nstlock);
-			return -1;
-		}
-		if (((l3id&0xff00)!=0xff00) && ((bc->l3_id&0xff00)==0xff00)) {
-			cb_log(4, stack->port, " --> Removing Process Id:%x on this port.\n", 0xff&bc->l3_id);
-			stack->procids[bc->l3_id&0xff] = 0 ;
-		}
-		cb_log(4, stack->port, "lib: Event_ind:CC_NEW_CR : very new L3ID  is %x\n",l3id );
-
-		bc->l3_id =l3id;
-		if (!bc->is_register_pool) {
-			cb_event(EVENT_NEW_L3ID, bc, glob_mgr->user_data);
-		}
-
-		free_msg(msg);
-		pthread_mutex_lock(&stack->nstlock);
-		return 0;
-
-	case DL_ESTABLISH | INDICATION:
-	case DL_ESTABLISH | CONFIRM:
-		cb_log(3, stack->port, "%% GOT L2 Activate Info.\n");
-
-		if (stack->ptp && stack->l2link) {
-			cb_log(0, stack->port, "%% GOT L2 Activate Info. but we're activated already.. this l2 is faulty, blocking port\n");
-			cb_event(EVENT_PORT_ALARM, &stack->bc[0], glob_mgr->user_data);
-		}
-
-		if (stack->ptp && !stack->restart_sent) {
-			/* make sure we restart the interface of the
-			 * other side */
-			stack->restart_sent=1;
-			misdn_lib_send_restart(stack->port, -1);
-
-		}
-
-		/* when we get the L2 UP, the L1 is UP definitely too*/
-		stack->l2link = 1;
-		stack->l2upcnt=0;
-
-		free_msg(msg);
-		pthread_mutex_lock(&stack->nstlock);
-		return 0;
-
-	case DL_RELEASE | INDICATION:
-	case DL_RELEASE | CONFIRM:
-		cb_log(3, stack->port, "%% GOT L2 DeActivate Info.\n");
-		if (stack->ptp) {
-
-			if (stack->l2upcnt>3) {
-				cb_log(0 , stack->port, "!!! Could not Get the L2 up after 3 Attempts!!!\n");
-			} else {
-				if (stack->l1link) {
-					misdn_lib_get_l2_up(stack);
-					stack->l2upcnt++;
-				}
-			}
-		}
-
-		stack->l2link = 0;
-		free_msg(msg);
-		pthread_mutex_lock(&stack->nstlock);
-		return 0;
-
-	default:
-		break;
-	}
-
-	/*  Parse Events and fire_up to App. */
-	event = isdn_msg_get_event(msgs_g, msg, 1);
-
-	bc = find_bc_by_l3id(stack, hh->dinfo);
-	if (!bc) {
-		cb_log(4, stack->port, " --> Didn't find BC so temporarily creating dummy BC (l3id:%x).\n", hh->dinfo);
-		misdn_make_dummy(&dummybc, stack->port,  hh->dinfo, stack->nt, 0);
-		bc = &dummybc;
-	}
-
-	isdn_msg_parse_event(msgs_g, msg, bc, 1);
-
-	switch (event) {
-	case EVENT_SETUP:
-		if (bc->channel <= 0 || bc->channel == 0xff) {
-			bc->channel = 0;
-		}
-
-		if (find_free_chan_in_stack(stack, bc, bc->channel, 0) < 0) {
-			goto ERR_NO_CHANNEL;
-		}
-		break;
-	case EVENT_RELEASE:
-	case EVENT_RELEASE_COMPLETE:
-		channel = bc->channel;
-		tmpcause = bc->cause;
-
-		empty_bc(bc);
-		bc->cause = tmpcause;
-		clean_up_bc(bc);
-
-		if (channel > 0)
-			empty_chan_in_stack(stack, channel);
-		bc->in_use = 0;
-		break;
-	default:
-		break;
-	}
-
-	if(!isdn_get_info(msgs_g, event, 1)) {
-		cb_log(4, stack->port, "Unknown Event Ind: prim %x dinfo %x\n", hh->prim, hh->dinfo);
-	} else {
-		if (reject) {
-			switch(bc->cause) {
-			case AST_CAUSE_USER_BUSY:
-				cb_log(1, stack->port, "Siemens Busy reject..\n");
-				break;
-			default:
-				break;
-			}
-		}
-		cb_event(event, bc, glob_mgr->user_data);
-	}
-
-	free_msg(msg);
-	pthread_mutex_lock(&stack->nstlock);
-	return 0;
-
-ERR_NO_CHANNEL:
-	cb_log(4, stack->port, "Patch from MEIDANIS:Sending RELEASE_COMPLETE %x (No free Chan for you..)\n", hh->dinfo);
-	dmsg = create_l3msg(CC_RELEASE_COMPLETE | REQUEST, MT_RELEASE_COMPLETE, hh->dinfo, sizeof(RELEASE_COMPLETE_t), 1);
-	pthread_mutex_lock(&stack->nstlock);
-	stack->nst.manager_l3(&stack->nst, dmsg);
-	free_msg(msg);
-	return 0;
-}
-
-
-static int handle_timers(msg_t* msg)
-{
-	iframe_t *frm= (iframe_t*)msg->data;
-	struct misdn_stack *stack;
-
-	/* Timer Stuff */
-	switch (frm->prim) {
-	case MGR_INITTIMER | CONFIRM:
-	case MGR_ADDTIMER | CONFIRM:
-	case MGR_DELTIMER | CONFIRM:
-	case MGR_REMOVETIMER | CONFIRM:
-		free_msg(msg);
-		return(1);
-	}
-
-
-
-	if (frm->prim==(MGR_TIMER | INDICATION) ) {
-		for (stack = glob_mgr->stack_list;
-		     stack;
-		     stack = stack->next) {
-			itimer_t *it;
-
-			if (!stack->nt) continue;
-
-			it = stack->nst.tlist;
-			/* find timer */
-			for(it=stack->nst.tlist;
-			    it;
-			    it=it->next) {
-				if (it->id == (int)frm->addr)
-					break;
-			}
-			if (it) {
-				mISDN_write_frame(stack->midev, msg->data, frm->addr,
-						  MGR_TIMER | RESPONSE, 0, 0, NULL, TIMEOUT_1SEC);
-				test_and_clear_bit(FLG_TIMER_RUNING, (long unsigned int *)&it->Flags);
-				pthread_mutex_lock(&stack->nstlock);
-				it->function(it->data);
-				pthread_mutex_unlock(&stack->nstlock);
-				free_msg(msg);
-				return 1;
-			}
-		}
-
-		cb_log(0, 0, "Timer Msg without Timer ??\n");
-		free_msg(msg);
-		return 1;
-	}
-
-	return 0;
-}
-
-
-
-void misdn_lib_tone_generator_start(struct misdn_bchannel *bc)
-{
-	bc->generate_tone=1;
-}
-
-void misdn_lib_tone_generator_stop(struct misdn_bchannel *bc)
-{
-	bc->generate_tone=0;
-}
-
-
-static int do_tone(struct misdn_bchannel *bc, int len)
-{
-	bc->tone_cnt=len;
-
-	if (bc->generate_tone) {
-		cb_event(EVENT_TONE_GENERATE, bc, glob_mgr->user_data);
-
-		if ( !bc->nojitter ) {
-			misdn_tx_jitter(bc,len);
-		}
-
-		return 1;
-	}
-
-	return 0;
-}
-
-
-#ifdef MISDN_SAVE_DATA
-static void misdn_save_data(int id, char *p1, int l1, char *p2, int l2)
-{
-	char n1[32],n2[32];
-	FILE *rx, *tx;
-
-	sprintf(n1,"/tmp/misdn-rx-%d.raw",id);
-	sprintf(n2,"/tmp/misdn-tx-%d.raw",id);
-
-	rx = fopen(n1,"a+");
-	tx = fopen(n2,"a+");
-
-	if (!rx || !tx) {
-		cb_log(0,0,"Couldn't open files: %s\n",strerror(errno));
-		if (rx)
-			fclose(rx);
-		if (tx)
-			fclose(tx);
-		return ;
-	}
-
-	fwrite(p1,1,l1,rx);
-	fwrite(p2,1,l2,tx);
-
-	fclose(rx);
-	fclose(tx);
-
-}
-#endif
-
-void misdn_tx_jitter(struct misdn_bchannel *bc, int len)
-{
-	char buf[4096 + mISDN_HEADER_LEN];
-	char *data=&buf[mISDN_HEADER_LEN];
-	iframe_t *txfrm= (iframe_t*)buf;
-	int jlen, r;
-
-	jlen=cb_jb_empty(bc,data,len);
-
-	if (jlen) {
-#ifdef MISDN_SAVE_DATA
-		misdn_save_data((bc->port*100+bc->channel), data, jlen, bc->bframe, bc->bframe_len);
-#endif
-		flip_buf_bits( data, jlen);
-
-		if (jlen < len) {
-			cb_log(1, bc->port, "Jitterbuffer Underrun. Got %d of expected %d\n", jlen, len);
-		}
-
-		txfrm->prim = DL_DATA|REQUEST;
-
-		txfrm->dinfo = 0;
-
-		txfrm->addr = bc->addr|FLG_MSG_DOWN; /*  | IF_DOWN; */
-
-		txfrm->len =jlen;
-		cb_log(9, bc->port, "Transmitting %d samples 2 misdn\n", txfrm->len);
-
-		r=mISDN_write( glob_mgr->midev, buf, txfrm->len + mISDN_HEADER_LEN, 8000 );
-	} else {
-#define MISDN_GEN_SILENCE
-#ifdef MISDN_GEN_SILENCE
-		int cnt=len/TONE_SILENCE_SIZE;
-		int rest=len%TONE_SILENCE_SIZE;
-		int i;
-
-		for (i=0; i<cnt; i++) {
-			memcpy(data, tone_silence_flip, TONE_SILENCE_SIZE );
-			data +=TONE_SILENCE_SIZE;
-		}
-
-		if (rest) {
-			memcpy(data, tone_silence_flip, rest);
-		}
-
-		txfrm->prim = DL_DATA|REQUEST;
-
-		txfrm->dinfo = 0;
-
-		txfrm->addr = bc->addr|FLG_MSG_DOWN; /*  | IF_DOWN; */
-
-		txfrm->len =len;
-		cb_log(5, bc->port, "Transmitting %d samples of silence to misdn\n", len);
-
-		r=mISDN_write( glob_mgr->midev, buf, txfrm->len + mISDN_HEADER_LEN, 8000 );
-#else
-		r = 0;
-#endif
-	}
-
-	if (r < 0) {
-		cb_log(1, bc->port, "Error in mISDN_write (%s)\n", strerror(errno));
-	}
-}
-
-static int handle_bchan(msg_t *msg)
-{
-	iframe_t *frm= (iframe_t*)msg->data;
-	struct misdn_bchannel *bc=find_bc_by_addr(frm->addr);
-	struct misdn_stack *stack;
-
-	if (!bc) {
-		cb_log(1,0,"handle_bchan: BC not found for prim:%x with addr:%x dinfo:%x\n", frm->prim, frm->addr, frm->dinfo);
-		return 0 ;
-	}
-
-	stack = get_stack_by_bc(bc);
-
-	if (!stack) {
-		cb_log(0, bc->port,"handle_bchan: STACK not found for prim:%x with addr:%x dinfo:%x\n", frm->prim, frm->addr, frm->dinfo);
-		return 0;
-	}
-
-	switch (frm->prim) {
-
-	case MGR_SETSTACK| CONFIRM:
-		cb_log(3, stack->port, "BCHAN: MGR_SETSTACK|CONFIRM pid:%d\n",bc->pid);
-		break;
-
-	case MGR_SETSTACK| INDICATION:
-		cb_log(3, stack->port, "BCHAN: MGR_SETSTACK|IND pid:%d\n",bc->pid);
-	break;
-
-	case MGR_DELLAYER| INDICATION:
-		cb_log(3, stack->port, "BCHAN: MGR_DELLAYER|IND pid:%d\n",bc->pid);
-		break;
-
-	case MGR_DELLAYER| CONFIRM:
-		cb_log(3, stack->port, "BCHAN: MGR_DELLAYER|CNF pid:%d\n",bc->pid);
-
-		bc->pid=0;
-		bc->addr=0;
-
-		free_msg(msg);
-		return 1;
-
-	case PH_ACTIVATE | INDICATION:
-	case DL_ESTABLISH | INDICATION:
-		cb_log(3, stack->port, "BCHAN: ACT Ind pid:%d\n", bc->pid);
-
-		free_msg(msg);
-		return 1;
-
-	case PH_ACTIVATE | CONFIRM:
-	case DL_ESTABLISH | CONFIRM:
-
-		cb_log(3, stack->port, "BCHAN: bchan ACT Confirm pid:%d\n",bc->pid);
-		free_msg(msg);
-
-		return 1;
-
-	case DL_ESTABLISH | REQUEST:
-		{
-			char buf[128];
-			mISDN_write_frame(stack->midev, buf, bc->addr | FLG_MSG_TARGET | FLG_MSG_DOWN,  DL_ESTABLISH | CONFIRM, 0,0, NULL, TIMEOUT_1SEC);
-		}
-		free_msg(msg);
-		return 1;
-
-	case DL_RELEASE|REQUEST:
-		{
-			char buf[128];
-			mISDN_write_frame(stack->midev, buf, bc->addr | FLG_MSG_TARGET | FLG_MSG_DOWN,  DL_RELEASE| CONFIRM, 0,0, NULL, TIMEOUT_1SEC);
-		}
-		free_msg(msg);
-		return 1;
-
-	case PH_DEACTIVATE | INDICATION:
-	case DL_RELEASE | INDICATION:
-		cb_log (3, stack->port, "BCHAN: DeACT Ind pid:%d\n",bc->pid);
-
-		free_msg(msg);
-		return 1;
-
-	case PH_DEACTIVATE | CONFIRM:
-	case DL_RELEASE | CONFIRM:
-		cb_log(3, stack->port, "BCHAN: DeACT Conf pid:%d\n",bc->pid);
-
-		free_msg(msg);
-		return 1;
-
-	case PH_CONTROL|INDICATION:
-	{
-		unsigned int *cont = (unsigned int *) &frm->data.p;
-
-		cb_log(4, stack->port,
-			"PH_CONTROL: channel:%d caller%d:\"%s\" <%s> dialed%d:%s \n",
-			bc->channel,
-			bc->caller.number_type,
-			bc->caller.name,
-			bc->caller.number,
-			bc->dialed.number_type,
-			bc->dialed.number);
-
-		if ((*cont & ~DTMF_TONE_MASK) == DTMF_TONE_VAL) {
-			int dtmf = *cont & DTMF_TONE_MASK;
-			cb_log(4, stack->port, " --> DTMF TONE: %c\n",dtmf);
-			bc->dtmf=dtmf;
-			cb_event(EVENT_DTMF_TONE, bc, glob_mgr->user_data);
-
-			free_msg(msg);
-			return 1;
-		}
-		if (*cont == BF_REJECT) {
-			cb_log(4, stack->port, " --> BF REJECT\n");
-			free_msg(msg);
-			return 1;
-		}
-		if (*cont == BF_ACCEPT) {
-			cb_log(4, stack->port, " --> BF ACCEPT\n");
-			free_msg(msg);
-			return 1;
-		}
-	}
-	break;
-
-	case PH_DATA|REQUEST:
-	case DL_DATA|REQUEST:
-		cb_log(0, stack->port, "DL_DATA REQUEST \n");
-		do_tone(bc, 64);
-
-		free_msg(msg);
-		return 1;
-
-
-	case PH_DATA|INDICATION:
-	case DL_DATA|INDICATION:
-	{
-		bc->bframe = (void*)&frm->data.i;
-		bc->bframe_len = frm->len;
-
-		/** Anyway flip the bufbits **/
-		if ( misdn_cap_is_speech(bc->capability) )
-			flip_buf_bits(bc->bframe, bc->bframe_len);
-
-
-		if (!bc->bframe_len) {
-			cb_log(2, stack->port, "DL_DATA INDICATION bc->addr:%x frm->addr:%x\n", bc->addr, frm->addr);
-			free_msg(msg);
-			return 1;
-		}
-
-		if ( (bc->addr&STACK_ID_MASK) != (frm->addr&STACK_ID_MASK) ) {
-			cb_log(2, stack->port, "DL_DATA INDICATION bc->addr:%x frm->addr:%x\n", bc->addr, frm->addr);
-			free_msg(msg);
-			return 1;
-		}
-
-#if MISDN_DEBUG
-		cb_log(0, stack->port, "DL_DATA INDICATION Len %d\n", frm->len);
-
-#endif
-
-		if ( (bc->bc_state == BCHAN_ACTIVATED) && frm->len > 0) {
-			int t;
-
-#ifdef MISDN_B_DEBUG
-			cb_log(0,bc->port,"do_tone START\n");
-#endif
-			t=do_tone(bc,frm->len);
-
-#ifdef MISDN_B_DEBUG
-			cb_log(0,bc->port,"do_tone STOP (%d)\n",t);
-#endif
-			if (  !t ) {
-				int i;
-
-				if ( misdn_cap_is_speech(bc->capability)) {
-					if ( !bc->nojitter ) {
-#ifdef MISDN_B_DEBUG
-						cb_log(0,bc->port,"tx_jitter START\n");
-#endif
-						misdn_tx_jitter(bc,frm->len);
-#ifdef MISDN_B_DEBUG
-						cb_log(0,bc->port,"tx_jitter STOP\n");
-#endif
-					}
-				}
-
-#ifdef MISDN_B_DEBUG
-				cb_log(0,bc->port,"EVENT_B_DATA START\n");
-#endif
-
-				i = cb_event(EVENT_BCHAN_DATA, bc, glob_mgr->user_data);
-#ifdef MISDN_B_DEBUG
-				cb_log(0,bc->port,"EVENT_B_DATA STOP\n");
-#endif
-
-				if (i<0) {
-					cb_log(10,stack->port,"cb_event returned <0\n");
-					/*clean_up_bc(bc);*/
-				}
-			}
-		}
-		free_msg(msg);
-		return 1;
-	}
-
-
-	case PH_CONTROL | CONFIRM:
-		cb_log(4, stack->port, "PH_CONTROL|CNF bc->addr:%x\n", frm->addr);
-		free_msg(msg);
-		return 1;
-
-	case PH_DATA | CONFIRM:
-	case DL_DATA|CONFIRM:
-#if MISDN_DEBUG
-
-		cb_log(0, stack->port, "Data confirmed\n");
-
-#endif
-		free_msg(msg);
-		return 1;
-	case DL_DATA|RESPONSE:
-#if MISDN_DEBUG
-		cb_log(0, stack->port, "Data response\n");
-
-#endif
-		break;
-	}
-
-	return 0;
-}
-
-
-
-static int handle_frm_nt(msg_t *msg)
-{
-	iframe_t *frm= (iframe_t*)msg->data;
-	struct misdn_stack *stack;
-	int err=0;
-
-	stack=find_stack_by_addr( frm->addr );
-
-
-
-	if (!stack || !stack->nt) {
-		return 0;
-	}
-
-
-	pthread_mutex_lock(&stack->nstlock);
-	if ((err=stack->nst.l1_l2(&stack->nst,msg))) {
-		pthread_mutex_unlock(&stack->nstlock);
-		if (nt_err_cnt > 0 ) {
-			if (nt_err_cnt < 100) {
-				nt_err_cnt++;
-				cb_log(0, stack->port, "NT Stack sends us error: %d \n", err);
-			} else if (nt_err_cnt < 105){
-				cb_log(0, stack->port, "NT Stack sends us error: %d over 100 times, so I'll stop this message\n", err);
-				nt_err_cnt = - 1;
-			}
-		}
-		free_msg(msg);
-		return 1;
-	}
-	pthread_mutex_unlock(&stack->nstlock);
-	return 1;
-}
-
-
-static int handle_frm_te(msg_t *msg)
-{
-	struct misdn_bchannel dummybc;
-	struct misdn_bchannel *bc;
-	iframe_t *frm;
-	struct misdn_stack *stack;
-	enum event_e event;
-	enum event_response_e response;
-	int ret;
-	int channel;
-	int tmpcause;
-	int tmp_out_cause;
-
-	frm = (iframe_t*) msg->data;
-	stack = find_stack_by_addr(frm->addr);
-	if (!stack || stack->nt) {
-		return 0;
-	}
-
-	cb_log(4, stack->port, "handle_frm_te: frm->addr:%x frm->prim:%x\n", frm->addr, frm->prim);
-
-	ret = handle_cr(stack, frm);
-	if (ret < 0) {
-		cb_log(3, stack->port, "handle_frm_te: handle_cr <0 prim:%x addr:%x\n", frm->prim, frm->addr);
-	}
-	if (ret) {
-		free_msg(msg);
-		return 1;
-	}
-
-	bc = find_bc_by_l3id(stack, frm->dinfo);
-	if (!bc) {
-		misdn_make_dummy(&dummybc, stack->port, 0, stack->nt, 0);
-		switch (frm->prim) {
-		case CC_RESTART | CONFIRM:
-			dummybc.l3_id = MISDN_ID_GLOBAL;
-			bc = &dummybc;
-			break;
-		case CC_SETUP | INDICATION:
-			dummybc.l3_id = frm->dinfo;
-			bc = &dummybc;
-
-			/* set a reasonable cause */
-			bc->out_cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
-			/* if we want to send something the flag must be set! */
-			bc->need_release_complete = 1;
-			misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE);
-
-			free_msg(msg);
-			return 1;
-		default:
-			if (frm->prim == (CC_FACILITY | INDICATION)) {
-				cb_log(5, stack->port, " --> Using Dummy BC for FACILITY\n");
-			} else {
-				cb_log(0, stack->port, " --> Didn't find BC so temporarily creating dummy BC (l3id:%x) on this port.\n", frm->dinfo);
-				dummybc.l3_id = frm->dinfo;
-			}
-			bc = &dummybc;
-			break;
-		}
-	}
-
-	event = isdn_msg_get_event(msgs_g, msg, 0);
-	isdn_msg_parse_event(msgs_g, msg, bc, 0);
-
-	/* Preprocess some Events */
-	ret = handle_event_te(bc, event, frm);
-	if (ret < 0) {
-		cb_log(0, stack->port, "couldn't handle event\n");
-		free_msg(msg);
-		return 1;
-	}
-
-	/* shoot up event to App: */
-	cb_log(5, stack->port, "lib Got Prim: Addr %x prim %x dinfo %x\n", frm->addr, frm->prim, frm->dinfo);
-
-	if (!isdn_get_info(msgs_g, event, 0)) {
-		cb_log(0, stack->port, "Unknown Event Ind: Addr:%x prim %x dinfo %x\n", frm->addr, frm->prim, frm->dinfo);
-		response = RESPONSE_OK;
-	} else {
-		response = cb_event(event, bc, glob_mgr->user_data);
-	}
-
-	switch (event) {
-	case EVENT_SETUP:
-		switch (response) {
-		case RESPONSE_IGNORE_SETUP_WITHOUT_CLOSE:
-			cb_log(0, stack->port, "TOTALLY IGNORING SETUP\n");
-			break;
-		case RESPONSE_IGNORE_SETUP:
-			cb_log(0, stack->port, "GOT IGNORE SETUP\n");
-			/* I think we should send CC_RELEASE_CR, but am not sure*/
-			bc->out_cause = AST_CAUSE_NORMAL_CLEARING;
-			/* fall through */
-		case RESPONSE_RELEASE_SETUP:
-			if (response == RESPONSE_RELEASE_SETUP) {
-				cb_log(0, stack->port, "GOT RELEASE SETUP\n");
-			}
-			misdn_lib_send_event(bc, EVENT_RELEASE_COMPLETE);
-			break;
-		case RESPONSE_OK:
-			cb_log(4, stack->port, "GOT SETUP OK\n");
-			break;
-		default:
-			cb_log(0, stack->port, "GOT UNKNOWN SETUP RESPONSE\n");
-			break;
-		}
-		break;
-	case EVENT_RELEASE_COMPLETE:
-		/* release bchannel only after we've announced the RELEASE_COMPLETE */
-		channel = bc->channel;
-		tmpcause = bc->cause;
-		tmp_out_cause = bc->out_cause;
-
-		empty_bc(bc);
-		bc->cause = tmpcause;
-		bc->out_cause = tmp_out_cause;
-		clean_up_bc(bc);
-		bc->in_use = 0;
-
-		if (tmpcause == AST_CAUSE_REQUESTED_CHAN_UNAVAIL) {
-			cb_log(0, stack->port, "**** Received CAUSE:%d, restarting channel %d\n", AST_CAUSE_REQUESTED_CHAN_UNAVAIL, channel);
-			misdn_lib_send_restart(stack->port, channel);
-		}
-		if (channel > 0) {
-			empty_chan_in_stack(stack, channel);
-		}
-		break;
-	case EVENT_RESTART:
-		cb_log(0, stack->port, "**** Received RESTART channel:%d\n", bc->restart_channel);
-		empty_chan_in_stack(stack, bc->restart_channel);
-		break;
-	default:
-		break;
-	}
-
-	cb_log(5, stack->port, "Freeing Msg on prim:%x \n", frm->prim);
-	free_msg(msg);
-	return 1;
-}
-
-
-static int handle_l1(msg_t *msg)
-{
-	iframe_t *frm = (iframe_t*) msg->data;
-	struct misdn_stack *stack = find_stack_by_addr(frm->addr);
-
-	if (!stack) return 0 ;
-
-	switch (frm->prim) {
-	case PH_ACTIVATE | CONFIRM:
-		/* we have to check for errors! */
-		if (frm->len) {
-			cb_log (3, stack->port, "L1: PH_ACTIVATE|REQUEST returned error!\n");
-			free_msg(msg);
-			return 1;
-		}
-		/* fall through */
-	case PH_ACTIVATE | INDICATION:
-		cb_log (3, stack->port, "L1: PH L1Link Up!\n");
-		stack->l1link=1;
-
-		if (stack->nt) {
-
-			pthread_mutex_lock(&stack->nstlock);
-			if (stack->nst.l1_l2(&stack->nst, msg))
-				free_msg(msg);
-			pthread_mutex_unlock(&stack->nstlock);
-
-			if (stack->ptp)
-				misdn_lib_get_l2_up(stack);
-		} else {
-			free_msg(msg);
-		}
-		return 1;
-
-	case PH_ACTIVATE | REQUEST:
-		free_msg(msg);
-		cb_log(3,stack->port,"L1: PH_ACTIVATE|REQUEST \n");
-		return 1;
-
-	case PH_DEACTIVATE | REQUEST:
-		free_msg(msg);
-		cb_log(3,stack->port,"L1: PH_DEACTIVATE|REQUEST \n");
-		return 1;
-
-	case PH_DEACTIVATE | CONFIRM:
-		/* we have to check for errors! */
-		if (frm->len) {
-			cb_log (3, stack->port, "L1: PH_DEACTIVATE|REQUEST returned error!\n");
-			free_msg(msg);
-			return 1;
-		}
-		/* fall through */
-	case PH_DEACTIVATE | INDICATION:
-		cb_log (3, stack->port, "L1: PH L1Link Down! \n");
-
-#if 0
-		for (i=0; i<=stack->b_num; i++) {
-			if (global_state == MISDN_INITIALIZED)  {
-				cb_event(EVENT_CLEANUP, &stack->bc[i], glob_mgr->user_data);
-			}
-		}
-#endif
-
-		if (stack->nt) {
-			pthread_mutex_lock(&stack->nstlock);
-			if (stack->nst.l1_l2(&stack->nst, msg))
-				free_msg(msg);
-			pthread_mutex_unlock(&stack->nstlock);
-		} else {
-			free_msg(msg);
-		}
-
-		stack->l1link=0;
-		stack->l2link=0;
-		return 1;
-	}
-
-	return 0;
-}
-
-static int handle_l2(msg_t *msg)
-{
-	iframe_t *frm = (iframe_t*) msg->data;
-
-	struct misdn_stack *stack = find_stack_by_addr(frm->addr);
-
-	if (!stack) {
-		return 0 ;
-	}
-
-	switch(frm->prim) {
-
-	case DL_ESTABLISH | REQUEST:
-		cb_log(1,stack->port,"DL_ESTABLISH|REQUEST \n");
-		free_msg(msg);
-		return 1;
-	case DL_RELEASE | REQUEST:
-		cb_log(1,stack->port,"DL_RELEASE|REQUEST \n");
-		free_msg(msg);
-		return 1;
-
-	case DL_ESTABLISH | INDICATION:
-	case DL_ESTABLISH | CONFIRM:
-	{
-		cb_log (3, stack->port, "L2: L2Link Up! \n");
-		if (stack->ptp && stack->l2link) {
-			cb_log (-1, stack->port, "L2: L2Link Up! but it's already UP.. must be faulty, blocking port\n");
-			cb_event(EVENT_PORT_ALARM, &stack->bc[0], glob_mgr->user_data);
-		}
-		stack->l2link=1;
-		free_msg(msg);
-		return 1;
-	}
-	break;
-
-	case DL_RELEASE | INDICATION:
-	case DL_RELEASE | CONFIRM:
-	{
-		cb_log (3, stack->port, "L2: L2Link Down! \n");
-		stack->l2link=0;
-
-		free_msg(msg);
-		return 1;
-	}
-	break;
-	}
-	return 0;
-}
-
-static int handle_mgmt(msg_t *msg)
-{
-	iframe_t *frm = (iframe_t*) msg->data;
-	struct misdn_stack *stack;
-
-	if ( (frm->addr == 0) && (frm->prim == (MGR_DELLAYER|CONFIRM)) ) {
-		cb_log(2, 0, "MGMT: DELLAYER|CONFIRM Addr: 0 !\n") ;
-		free_msg(msg);
-		return 1;
-	}
-
-	stack = find_stack_by_addr(frm->addr);
-
-	if (!stack) {
-		if (frm->prim == (MGR_DELLAYER|CONFIRM)) {
-			cb_log(2, 0, "MGMT: DELLAYER|CONFIRM Addr: %x !\n",
-					frm->addr) ;
-			free_msg(msg);
-			return 1;
-		}
-
-		return 0;
-	}
-
-	switch(frm->prim) {
-	case MGR_SHORTSTATUS | INDICATION:
-	case MGR_SHORTSTATUS | CONFIRM:
-		cb_log(5, stack->port, "MGMT: Short status dinfo %x\n",frm->dinfo);
-
-		switch (frm->dinfo) {
-		case SSTATUS_L1_ACTIVATED:
-			cb_log(3, stack->port, "MGMT: SSTATUS: L1_ACTIVATED \n");
-			stack->l1link=1;
-
-			break;
-		case SSTATUS_L1_DEACTIVATED:
-			cb_log(3, stack->port, "MGMT: SSTATUS: L1_DEACTIVATED \n");
-			stack->l1link=0;
-#if 0
-			clear_l3(stack);
-#endif
-			break;
-
-		case SSTATUS_L2_ESTABLISHED:
-			cb_log(3, stack->port, "MGMT: SSTATUS: L2_ESTABLISH \n");
-			stack->l2link=1;
-			break;
-
-		case SSTATUS_L2_RELEASED:
-			cb_log(3, stack->port, "MGMT: SSTATUS: L2_RELEASED \n");
-			stack->l2link=0;
-			break;
-		}
-
-		free_msg(msg);
-		return 1;
-
-	case MGR_SETSTACK | INDICATION:
-		cb_log(4, stack->port, "MGMT: SETSTACK|IND dinfo %x\n",frm->dinfo);
-		free_msg(msg);
-		return 1;
-	case MGR_DELLAYER | CONFIRM:
-		cb_log(4, stack->port, "MGMT: DELLAYER|CNF dinfo %x\n",frm->dinfo) ;
-		free_msg(msg);
-		return 1;
-
-	}
-
-	/*
-	if ( (frm->prim & 0x0f0000) ==  0x0f0000) {
-	cb_log(5, 0, "$$$ MGMT FRAME: prim %x addr %x dinfo %x\n",frm->prim, frm->addr, frm->dinfo) ;
-	free_msg(msg);
-	return 1;
-	} */
-
-	return 0;
-}
-
-
-static msg_t *fetch_msg(int midev)
-{
-	msg_t *msg=alloc_msg(MAX_MSG_SIZE);
-	int r;
-
-	if (!msg) {
-		cb_log(0, 0, "fetch_msg: alloc msg failed !!");
-		return NULL;
-	}
-
-	AGAIN:
-		r=mISDN_read(midev,msg->data,MAX_MSG_SIZE, TIMEOUT_10SEC);
-		msg->len=r;
-
-		if (r==0) {
-			free_msg(msg); /* danger, cause usually freeing in main_loop */
-			cb_log(6,0,"Got empty Msg..\n");
-			return NULL;
-		}
-
-		if (r<0) {
-			if (errno == EAGAIN) {
-				/*we wait for mISDN here*/
-				cb_log(4,0,"mISDN_read wants us to wait\n");
-				usleep(5000);
-				goto AGAIN;
-			}
-
-			cb_log(0,0,"mISDN_read returned :%d error:%s (%d)\n",r,strerror(errno),errno);
-		}
-
-#if 0
-               if  (!(frm->prim == (DL_DATA|INDICATION) )|| (frm->prim == (PH_DATA|INDICATION)))
-                       cb_log(0,0,"prim: %x dinfo:%x addr:%x msglen:%d frm->len:%d\n",frm->prim, frm->dinfo, frm->addr, msg->len,frm->len );
-#endif
-		return msg;
-}
-
-void misdn_lib_isdn_l1watcher(int port)
-{
-	struct misdn_stack *stack;
-
-	for (stack = glob_mgr->stack_list; stack && (stack->port != port); stack = stack->next)
-		;
-
-	if (stack) {
-		cb_log(4, port, "Checking L1 State\n");
-		if (!stack->l1link) {
-			cb_log(4, port, "L1 State Down, trying to get it up again\n");
-			misdn_lib_get_short_status(stack);
-			misdn_lib_get_l1_up(stack);
-			misdn_lib_get_l2_up(stack);
-		}
-	}
-}
-
-/* This is a thread */
-static void misdn_lib_isdn_event_catcher(void *arg)
-{
-	struct misdn_lib *mgr = arg;
-	int zero_frm=0 , fff_frm=0 ;
-	int midev= mgr->midev;
-	int port=0;
-
-	while (1) {
-		msg_t *msg = fetch_msg(midev);
-		iframe_t *frm;
-
-
-		if (!msg) continue;
-
-		frm = (iframe_t*) msg->data;
-
-		/** When we make a call from NT2Ast we get these frames **/
-		if (frm->len == 0 && frm->addr == 0 && frm->dinfo == 0 && frm->prim == 0 ) {
-			zero_frm++;
-			free_msg(msg);
-			continue;
-		} else {
-			if (zero_frm) {
-				cb_log(0, port, "*** Alert: %d zero_frms caught\n", zero_frm);
-				zero_frm = 0 ;
-			}
-		}
-
-		/** I get this sometimes after setup_bc **/
-		if (frm->len == 0 &&  frm->dinfo == 0 && frm->prim == 0xffffffff ) {
-			fff_frm++;
-			free_msg(msg);
-			continue;
-		} else {
-			if (fff_frm) {
-				cb_log(0, port, "*** Alert: %d fff_frms caught\n", fff_frm);
-				fff_frm = 0 ;
-			}
-		}
-
-		manager_isdn_handler(frm, msg);
-	}
-
-}
-
-
-/** App Interface **/
-
-static int te_lib_init(void)
-{
-	char buff[1025] = "";
-	iframe_t *frm = (iframe_t *) buff;
-	int midev;
-	int ret;
-
-	midev = mISDN_open();
-	if (midev <= 0) {
-		return midev;
-	}
-
-	/* create entity for layer 3 TE-mode */
-	mISDN_write_frame(midev, buff, 0, MGR_NEWENTITY | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
-
-	ret = mISDN_read_frame(midev, frm, sizeof(iframe_t), 0, MGR_NEWENTITY | CONFIRM, TIMEOUT_1SEC);
-	entity = frm->dinfo & 0xffff;
-	if (ret < mISDN_HEADER_LEN || !entity) {
-		fprintf(stderr, "cannot request MGR_NEWENTITY from mISDN: %s\n", strerror(errno));
-		mISDN_close(midev);
-		return -1;
-	}
-
-	return midev;
-}
-
-void te_lib_destroy(int midev)
-{
-	char buf[1024];
-	mISDN_write_frame(midev, buf, 0, MGR_DELENTITY | REQUEST, entity, 0, NULL, TIMEOUT_1SEC);
-
-	cb_log(4, 0, "Entity deleted\n");
-	mISDN_close(midev);
-	cb_log(4, 0, "midev closed\n");
-}
-
-struct misdn_bchannel *manager_find_bc_by_pid(int pid)
-{
-	struct misdn_stack *stack;
-	int i;
-
-	for (stack = glob_mgr->stack_list; stack; stack = stack->next) {
-		for (i = 0; i <= stack->b_num; i++) {
-			if (stack->bc[i].in_use && stack->bc[i].pid == pid) {
-				return &stack->bc[i];
-			}
-		}
-	}
-
-	return NULL;
-}
-
-static int test_inuse(struct misdn_bchannel *bc)
-{
-	struct timeval now;
-
-	if (!bc->in_use) {
-		gettimeofday(&now, NULL);
-		if (bc->last_used.tv_sec == now.tv_sec
-			&& misdn_lib_port_is_pri(bc->port)) {
-			cb_log(2, bc->port, "channel with stid:%x for one second still in use! (n:%d lu:%d)\n",
-				bc->b_stid, (int) now.tv_sec, (int) bc->last_used.tv_sec);
-			return 1;
-		}
-
-		cb_log(3,bc->port, "channel with stid:%x not in use!\n", bc->b_stid);
-		return 0;
-	}
-
-	cb_log(2,bc->port, "channel with stid:%x in use!\n", bc->b_stid);
-	return 1;
-}
-
-
-static void prepare_bc(struct misdn_bchannel*bc, int channel)
-{
-	bc->channel = channel;
-	bc->channel_preselected = channel?1:0;
-	bc->need_disconnect=1;
-	bc->need_release=1;
-	bc->need_release_complete=1;
-	bc->cause = AST_CAUSE_NORMAL_CLEARING;
-
-	if (++mypid>5000) mypid=1;
-	bc->pid=mypid;
-
-	bc->in_use = 1;
-}
-
-struct misdn_bchannel *misdn_lib_get_free_bc(int port, int channel, int inout, int dec)
-{
-	struct misdn_stack *stack;
-	int i;
-	int maxnum;
-
-	if (channel < 0 || channel > MAX_BCHANS) {
-		cb_log(0, port, "Requested channel out of bounds (%d)\n", channel);
-		return NULL;
-	}
-
-	/* Find the port stack structure */
-	stack = find_stack_by_port(port);
-	if (!stack) {
-		cb_log(0, port, "Port is not configured (%d)\n", port);
-		return NULL;
-	}
-
-	if (stack->blocked) {
-		cb_log(0, port, "Port is blocked\n");
-		return NULL;
-	}
-
-	pthread_mutex_lock(&stack->st_lock);
-	if (channel > 0) {
-		if (channel <= stack->b_num) {
-			for (i = 0; i < stack->b_num; i++) {
-				if (stack->bc[i].channel == channel) {
-					if (test_inuse(&stack->bc[i])) {
-						pthread_mutex_unlock(&stack->st_lock);
-						cb_log(0, port, "Requested channel:%d on port:%d is already in use\n", channel, port);
-						return NULL;
-					} else {
-						prepare_bc(&stack->bc[i], channel);
-						pthread_mutex_unlock(&stack->st_lock);
-						return &stack->bc[i];
-					}
-				}
-			}
-		} else {
-			pthread_mutex_unlock(&stack->st_lock);
-			cb_log(0, port, "Requested channel:%d is out of bounds on port:%d\n", channel, port);
-			return NULL;
-		}
-	}
-
-	/* Note: channel == 0 here */
-	maxnum = (inout && !stack->pri && !stack->ptp) ? stack->b_num + 1 : stack->b_num;
-	if (dec) {
-		for (i = maxnum - 1; i >= 0; --i) {
-			if (!test_inuse(&stack->bc[i])) {
-				/* 3. channel on bri means CW*/
-				if (!stack->pri && i == stack->b_num) {
-					stack->bc[i].cw = 1;
-				}
-
-				prepare_bc(&stack->bc[i], channel);
-				stack->bc[i].dec = 1;
-				pthread_mutex_unlock(&stack->st_lock);
-				return &stack->bc[i];
-			}
-		}
-	} else {
-		for (i = 0; i < maxnum; ++i) {
-			if (!test_inuse(&stack->bc[i])) {
-				/* 3. channel on bri means CW */
-				if (!stack->pri && i == stack->b_num) {
-					stack->bc[i].cw = 1;
-				}
-
-				prepare_bc(&stack->bc[i], channel);
-				pthread_mutex_unlock(&stack->st_lock);
-				return &stack->bc[i];
-			}
-		}
-	}
-	pthread_mutex_unlock(&stack->st_lock);
-
-	cb_log(1, port, "There is no free channel on port (%d)\n", port);
-	return NULL;
-}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \brief Allocate a B channel struct from the REGISTER pool
- *
- * \param port Logical port number
- *
- * \retval B channel struct on success.
- * \retval NULL on error.
- */
-struct misdn_bchannel *misdn_lib_get_register_bc(int port)
-{
-	struct misdn_stack *stack;
-	struct misdn_bchannel *bc;
-	unsigned index;
-
-	/* Find the port stack structure */
-	stack = find_stack_by_port(port);
-	if (!stack) {
-		cb_log(0, port, "Port is not configured (%d)\n", port);
-		return NULL;
-	}
-
-	if (stack->blocked) {
-		cb_log(0, port, "Port is blocked\n");
-		return NULL;
-	}
-
-	pthread_mutex_lock(&stack->st_lock);
-	for (index = MAX_BCHANS + 1; index < ARRAY_LEN(stack->bc); ++index) {
-		bc = &stack->bc[index];
-		if (!test_inuse(bc)) {
-			prepare_bc(bc, 0);
-			bc->need_disconnect = 0;
-			bc->need_release = 0;
-			pthread_mutex_unlock(&stack->st_lock);
-			return bc;
-		}
-	}
-	pthread_mutex_unlock(&stack->st_lock);
-
-	cb_log(1, port, "There is no free REGISTER link on port (%d)\n", port);
-	return NULL;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-/*!
- * \internal
- * \brief Convert the facility function enum value into a string.
- *
- * \return String version of the enum value
- */
-static const char *fac2str(enum FacFunction facility)
-{
-	static const struct {
-		enum FacFunction facility;
-		char *name;
-	} arr[] = {
-/* *INDENT-OFF* */
-		{ Fac_None, "Fac_None" },
-#if defined(AST_MISDN_ENHANCEMENTS)
-		{ Fac_ERROR, "Fac_ERROR" },
-		{ Fac_RESULT, "Fac_RESULT" },
-		{ Fac_REJECT, "Fac_REJECT" },
-
-		{ Fac_ActivationDiversion, "Fac_ActivationDiversion" },
-		{ Fac_DeactivationDiversion, "Fac_DeactivationDiversion" },
-		{ Fac_ActivationStatusNotificationDiv, "Fac_ActivationStatusNotificationDiv" },
-		{ Fac_DeactivationStatusNotificationDiv, "Fac_DeactivationStatusNotificationDiv" },
-		{ Fac_InterrogationDiversion, "Fac_InterrogationDiversion" },
-		{ Fac_DiversionInformation, "Fac_DiversionInformation" },
-		{ Fac_CallDeflection, "Fac_CallDeflection" },
-		{ Fac_CallRerouteing, "Fac_CallRerouteing" },
-		{ Fac_DivertingLegInformation2, "Fac_DivertingLegInformation2" },
-		{ Fac_InterrogateServedUserNumbers, "Fac_InterrogateServedUserNumbers" },
-		{ Fac_DivertingLegInformation1, "Fac_DivertingLegInformation1" },
-		{ Fac_DivertingLegInformation3, "Fac_DivertingLegInformation3" },
-
-		{ Fac_EctExecute, "Fac_EctExecute" },
-		{ Fac_ExplicitEctExecute, "Fac_ExplicitEctExecute" },
-		{ Fac_RequestSubaddress, "Fac_RequestSubaddress" },
-		{ Fac_SubaddressTransfer, "Fac_SubaddressTransfer" },
-		{ Fac_EctLinkIdRequest, "Fac_EctLinkIdRequest" },
-		{ Fac_EctInform, "Fac_EctInform" },
-		{ Fac_EctLoopTest, "Fac_EctLoopTest" },
-
-		{ Fac_ChargingRequest, "Fac_ChargingRequest" },
-		{ Fac_AOCSCurrency, "Fac_AOCSCurrency" },
-		{ Fac_AOCSSpecialArr, "Fac_AOCSSpecialArr" },
-		{ Fac_AOCDCurrency, "Fac_AOCDCurrency" },
-		{ Fac_AOCDChargingUnit, "Fac_AOCDChargingUnit" },
-		{ Fac_AOCECurrency, "Fac_AOCECurrency" },
-		{ Fac_AOCEChargingUnit, "Fac_AOCEChargingUnit" },
-
-		{ Fac_StatusRequest, "Fac_StatusRequest" },
-
-		{ Fac_CallInfoRetain, "Fac_CallInfoRetain" },
-		{ Fac_EraseCallLinkageID, "Fac_EraseCallLinkageID" },
-		{ Fac_CCBSDeactivate, "Fac_CCBSDeactivate" },
-		{ Fac_CCBSErase, "Fac_CCBSErase" },
-		{ Fac_CCBSRemoteUserFree, "Fac_CCBSRemoteUserFree" },
-		{ Fac_CCBSCall, "Fac_CCBSCall" },
-		{ Fac_CCBSStatusRequest, "Fac_CCBSStatusRequest" },
-		{ Fac_CCBSBFree, "Fac_CCBSBFree" },
-		{ Fac_CCBSStopAlerting, "Fac_CCBSStopAlerting" },
-
-		{ Fac_CCBSRequest, "Fac_CCBSRequest" },
-		{ Fac_CCBSInterrogate, "Fac_CCBSInterrogate" },
-
-		{ Fac_CCNRRequest, "Fac_CCNRRequest" },
-		{ Fac_CCNRInterrogate, "Fac_CCNRInterrogate" },
-
-		{ Fac_CCBS_T_Call, "Fac_CCBS_T_Call" },
-		{ Fac_CCBS_T_Suspend, "Fac_CCBS_T_Suspend" },
-		{ Fac_CCBS_T_Resume, "Fac_CCBS_T_Resume" },
-		{ Fac_CCBS_T_RemoteUserFree, "Fac_CCBS_T_RemoteUserFree" },
-		{ Fac_CCBS_T_Available, "Fac_CCBS_T_Available" },
-
-		{ Fac_CCBS_T_Request, "Fac_CCBS_T_Request" },
-
-		{ Fac_CCNR_T_Request, "Fac_CCNR_T_Request" },
-
-#else
-
-		{ Fac_CFActivate, "Fac_CFActivate" },
-		{ Fac_CFDeactivate, "Fac_CFDeactivate" },
-		{ Fac_CD, "Fac_CD" },
-
-		{ Fac_AOCDCurrency, "Fac_AOCDCurrency" },
-		{ Fac_AOCDChargingUnit, "Fac_AOCDChargingUnit" },
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-/* *INDENT-ON* */
-	};
-
-	unsigned index;
-
-	for (index = 0; index < ARRAY_LEN(arr); ++index) {
-		if (arr[index].facility == facility) {
-			return arr[index].name;
-		}
-	}
-
-	return "unknown";
-}
-
-void misdn_lib_log_ies(struct misdn_bchannel *bc)
-{
-	struct misdn_stack *stack;
-
-	if (!bc) return;
-
-	stack = get_stack_by_bc(bc);
-
-	if (!stack) return;
-
-	cb_log(2, stack->port,
-		" --> channel:%d mode:%s cause:%d ocause:%d\n",
-		bc->channel,
-		stack->nt ? "NT" : "TE",
-		bc->cause,
-		bc->out_cause);
-
-	cb_log(2, stack->port,
-		" --> info_dad:%s dialed numtype:%d plan:%d\n",
-		bc->info_dad,
-		bc->dialed.number_type,
-		bc->dialed.number_plan);
-
-	cb_log(2, stack->port,
-		" --> caller:\"%s\" <%s> type:%d plan:%d pres:%d screen:%d\n",
-		bc->caller.name,
-		bc->caller.number,
-		bc->caller.number_type,
-		bc->caller.number_plan,
-		bc->caller.presentation,
-		bc->caller.screening);
-
-	cb_log(2, stack->port,
-		" --> redirecting-from:\"%s\" <%s> type:%d plan:%d pres:%d screen:%d\n",
-		bc->redirecting.from.name,
-		bc->redirecting.from.number,
-		bc->redirecting.from.number_type,
-		bc->redirecting.from.number_plan,
-		bc->redirecting.from.presentation,
-		bc->redirecting.from.screening);
-	cb_log(2, stack->port,
-		" --> redirecting-to:\"%s\" <%s> type:%d plan:%d pres:%d screen:%d\n",
-		bc->redirecting.to.name,
-		bc->redirecting.to.number,
-		bc->redirecting.to.number_type,
-		bc->redirecting.to.number_plan,
-		bc->redirecting.to.presentation,
-		bc->redirecting.to.screening);
-	cb_log(2, stack->port,
-		" --> redirecting reason:%d count:%d\n",
-		bc->redirecting.reason,
-		bc->redirecting.count);
-
-	cb_log(2, stack->port,
-		" --> connected:\"%s\" <%s> type:%d plan:%d pres:%d screen:%d\n",
-		bc->connected.name,
-		bc->connected.number,
-		bc->connected.number_type,
-		bc->connected.number_plan,
-		bc->connected.presentation,
-		bc->connected.screening);
-
-	cb_log(3, stack->port, " --> caps:%s pi:%x keypad:%s sending_complete:%d\n", bearer2str(bc->capability),bc->progress_indicator, bc->keypad, bc->sending_complete);
-
-	cb_log(4, stack->port, " --> set_pres:%d pres:%d\n", bc->set_presentation, bc->presentation);
-
-	cb_log(4, stack->port, " --> addr:%x l3id:%x b_stid:%x layer_id:%x\n", bc->addr, bc->l3_id, bc->b_stid, bc->layer_id);
-
-	cb_log(4, stack->port, " --> facility in:%s out:%s\n", fac2str(bc->fac_in.Function), fac2str(bc->fac_out.Function));
-
-	cb_log(5, stack->port, " --> urate:%d rate:%d mode:%d user1:%d\n", bc->urate, bc->rate, bc->mode,bc->user1);
-
-	cb_log(5, stack->port, " --> bc:%p h:%d sh:%d\n", bc, bc->holded, bc->stack_holder);
-}
-
-
-#define RETURN(a,b) {retval=a; goto b;}
-
-static void misdn_send_lock(struct misdn_bchannel *bc)
-{
-	//cb_log(0,bc->port,"Locking bc->pid:%d\n", bc->pid);
-	if (bc->send_lock)
-		pthread_mutex_lock(&bc->send_lock->lock);
-}
-
-static void misdn_send_unlock(struct misdn_bchannel *bc)
-{
-	//cb_log(0,bc->port,"UnLocking bc->pid:%d\n", bc->pid);
-	if (bc->send_lock)
-		pthread_mutex_unlock(&bc->send_lock->lock);
-}
-
-int misdn_lib_send_event(struct misdn_bchannel *bc, enum event_e event )
-{
-	msg_t *msg;
-	struct misdn_bchannel *bc2;
-	struct misdn_bchannel *held_bc;
-	struct misdn_stack *stack;
-	int retval = 0;
-	int channel;
-	int tmpcause;
-	int tmp_out_cause;
-
-	if (!bc)
-		RETURN(-1,OUT_POST_UNLOCK);
-
-	stack = get_stack_by_bc(bc);
-	if (!stack) {
-		cb_log(0,bc->port,
-			"SENDEVENT: no Stack for event:%s caller:\"%s\" <%s> dialed:%s \n",
-			isdn_get_info(msgs_g, event, 0),
-			bc->caller.name,
-			bc->caller.number,
-			bc->dialed.number);
-		RETURN(-1,OUT);
-	}
-
-	misdn_send_lock(bc);
-
-
-	cb_log(6,stack->port,"SENDEVENT: stack->nt:%d stack->upperid:%x\n",stack->nt, stack->upper_id);
-
-	if ( stack->nt && !stack->l1link) {
-		misdn_lib_get_l1_up(stack);
-	}
-
-	cb_log(1, stack->port,
-		"I SEND:%s caller:\"%s\" <%s> dialed:%s pid:%d\n",
-		isdn_get_info(msgs_g, event, 0),
-		bc->caller.name,
-		bc->caller.number,
-		bc->dialed.number,
-		bc->pid);
-	cb_log(4, stack->port, " --> bc_state:%s\n",bc_state2str(bc->bc_state));
-	misdn_lib_log_ies(bc);
-
-	switch (event) {
-	case EVENT_REGISTER:
-	case EVENT_SETUP:
-		if (create_process(glob_mgr->midev, bc) < 0) {
-			cb_log(0, stack->port, " No free channel at the moment @ send_event\n");
-
-			RETURN(-ENOCHAN,OUT);
-		}
-		break;
-
-	case EVENT_PROGRESS:
-	case EVENT_ALERTING:
-	case EVENT_PROCEEDING:
-	case EVENT_SETUP_ACKNOWLEDGE:
-	case EVENT_CONNECT:
-		if (!stack->nt) {
-			if (stack->ptp) {
-				setup_bc(bc);
-			}
-			break;
-		}
-		/* fall through */
-
-	case EVENT_RETRIEVE_ACKNOWLEDGE:
-		if (stack->nt) {
-			if (bc->channel <=0 ) { /*  else we have the channel already */
-				if (find_free_chan_in_stack(stack, bc, 0, 0)<0) {
-					cb_log(0, stack->port, " No free channel at the moment\n");
-					/*FIXME: add disconnect*/
-					RETURN(-ENOCHAN,OUT);
-				}
-			}
-			/* Its that i generate channels */
-		}
-
-		retval=setup_bc(bc);
-		if (retval == -EINVAL) {
-			cb_log(0,bc->port,"send_event: setup_bc failed\n");
-		}
-
-		if (misdn_cap_is_speech(bc->capability)) {
-			if ((event==EVENT_CONNECT)||(event==EVENT_RETRIEVE_ACKNOWLEDGE)) {
-				if ( *bc->crypt_key ) {
-					cb_log(4, stack->port,
-						" --> ENABLING BLOWFISH channel:%d caller%d:\"%s\" <%s> dialed%d:%s\n",
-						bc->channel,
-						bc->caller.number_type,
-						bc->caller.name,
-						bc->caller.number,
-						bc->dialed.number_type,
-						bc->dialed.number);
-
-					manager_ph_control_block(bc,  BF_ENABLE_KEY, bc->crypt_key, strlen(bc->crypt_key) );
-				}
-
-				if (!bc->nodsp) manager_ph_control(bc,  DTMF_TONE_START, 0);
-				manager_ec_enable(bc);
-
-				if (bc->txgain != 0) {
-					cb_log(4, stack->port,  "--> Changing txgain to %d\n", bc->txgain);
-					manager_ph_control(bc, VOL_CHANGE_TX, bc->txgain);
-				}
-
-				if ( bc->rxgain != 0 ) {
-					cb_log(4, stack->port,  "--> Changing rxgain to %d\n", bc->rxgain);
-					manager_ph_control(bc, VOL_CHANGE_RX, bc->rxgain);
-				}
-			}
-		}
-		break;
-
-	case EVENT_HOLD_ACKNOWLEDGE:
-		held_bc = malloc(sizeof(struct misdn_bchannel));
-		if (!held_bc) {
-			cb_log(0, bc->port, "Could not allocate held_bc!!!\n");
-			RETURN(-1,OUT);
-		}
-
-		/* backup the bc and put it in storage */
-		*held_bc = *bc;
-		held_bc->holded = 1;
-		held_bc->channel = 0;/* A held call does not have a channel anymore. */
-		held_bc->channel_preselected = 0;
-		held_bc->channel_found = 0;
-		bc_state_change(held_bc, BCHAN_CLEANED);
-		stack_holder_add(stack, held_bc);
-
-		/* kill the bridge and clean the real b-channel record */
-		if (stack->nt) {
-			if (bc->bc_state == BCHAN_BRIDGED) {
-				misdn_split_conf(bc,bc->conf_id);
-				bc2 = find_bc_by_confid(bc->conf_id);
-				if (!bc2) {
-					cb_log(0,bc->port,"We have no second bc in bridge???\n");
-				} else {
-					misdn_split_conf(bc2,bc->conf_id);
-				}
-			}
-
-			channel = bc->channel;
-
-			empty_bc(bc);
-			clean_up_bc(bc);
-
-			if (channel>0)
-				empty_chan_in_stack(stack,channel);
-
-			bc->in_use=0;
-		}
-		break;
-
-	/* finishing the channel eh ? */
-	case EVENT_DISCONNECT:
-		if (!bc->need_disconnect) {
-			cb_log(0, bc->port, " --> we have already sent DISCONNECT\n");
-			RETURN(-1,OUT);
-		}
-		/* IE cause is mandatory for DISCONNECT, but optional for the answers to DISCONNECT.
-		 * We must initialize cause, so it is later correctly indicated to ast_channel
-		 * in case the answer does not include one!
-		 */
-		bc->cause = bc->out_cause;
-
-		bc->need_disconnect=0;
-		break;
-	case EVENT_RELEASE:
-		if (!bc->need_release) {
-			cb_log(0, bc->port, " --> we have already sent RELEASE\n");
-			RETURN(-1,OUT);
-		}
-		bc->need_disconnect=0;
-		bc->need_release=0;
-		break;
-	case EVENT_RELEASE_COMPLETE:
-		if (!bc->need_release_complete) {
-			cb_log(0, bc->port, " --> we have already sent RELEASE_COMPLETE\n");
-			RETURN(-1,OUT);
-		}
-		bc->need_disconnect=0;
-		bc->need_release=0;
-		bc->need_release_complete=0;
-
-		if (!stack->nt) {
-			/* create cleanup in TE */
-			channel = bc->channel;
-			tmpcause = bc->cause;
-			tmp_out_cause = bc->out_cause;
-
-			empty_bc(bc);
-			bc->cause=tmpcause;
-			bc->out_cause=tmp_out_cause;
-			clean_up_bc(bc);
-
-			if (channel>0)
-				empty_chan_in_stack(stack,channel);
-
-			bc->in_use=0;
-		}
-		break;
-
-	case EVENT_CONNECT_ACKNOWLEDGE:
-		if ( bc->nt || misdn_cap_is_speech(bc->capability)) {
-			int retval=setup_bc(bc);
-			if (retval == -EINVAL){
-				cb_log(0,bc->port,"send_event: setup_bc failed\n");
-
-			}
-		}
-
-		if (misdn_cap_is_speech(bc->capability)) {
-			if (  !bc->nodsp) manager_ph_control(bc,  DTMF_TONE_START, 0);
-			manager_ec_enable(bc);
-
-			if ( bc->txgain != 0 ) {
-				cb_log(4, stack->port, "--> Changing txgain to %d\n", bc->txgain);
-				manager_ph_control(bc, VOL_CHANGE_TX, bc->txgain);
-			}
-			if ( bc->rxgain != 0 ) {
-				cb_log(4, stack->port, "--> Changing rxgain to %d\n", bc->rxgain);
-				manager_ph_control(bc, VOL_CHANGE_RX, bc->rxgain);
-			}
-		}
-		break;
-
-	default:
-		break;
-	}
-
-	/* Later we should think about sending bchannel data directly to misdn. */
-	msg = isdn_msg_build_event(msgs_g, bc, event, stack->nt);
-	if (!msg) {
-		/*
-		 * The message was not built.
-		 *
-		 * NOTE:  The only time that the message will fail to build
-		 * is because the requested FACILITY message is not supported.
-		 * A failed malloc() results in exit() being called.
-		 */
-		RETURN(-1, OUT);
-	} else {
-		msg_queue_tail(&stack->downqueue, msg);
-		sem_post(&glob_mgr->new_msg);
-	}
-
-OUT:
-	misdn_send_unlock(bc);
-
-OUT_POST_UNLOCK:
-	return retval;
-}
-
-
-static int handle_err(msg_t *msg)
-{
-	iframe_t *frm = (iframe_t*) msg->data;
-
-
-	if (!frm->addr) {
-		static int cnt=0;
-		if (!cnt)
-			cb_log(0,0,"mISDN Msg without Address pr:%x dinfo:%x\n",frm->prim,frm->dinfo);
-		cnt++;
-		if (cnt>100) {
-			cb_log(0,0,"mISDN Msg without Address pr:%x dinfo:%x (already more than 100 of them)\n",frm->prim,frm->dinfo);
-			cnt=0;
-		}
-
-		free_msg(msg);
-		return 1;
-
-	}
-
-	switch (frm->prim) {
-		case MGR_SETSTACK|INDICATION:
-			return handle_bchan(msg);
-		break;
-
-		case MGR_SETSTACK|CONFIRM:
-		case MGR_CLEARSTACK|CONFIRM:
-			free_msg(msg) ;
-			return 1;
-		break;
-
-		case DL_DATA|CONFIRM:
-			cb_log(4,0,"DL_DATA|CONFIRM\n");
-			free_msg(msg);
-			return 1;
-
-		case PH_CONTROL|CONFIRM:
-			cb_log(4,0,"PH_CONTROL|CONFIRM\n");
-			free_msg(msg);
-			return 1;
-
-		case DL_DATA|INDICATION:
-		{
-			int port=(frm->addr&MASTER_ID_MASK) >> 8;
-			int channel=(frm->addr&CHILD_ID_MASK) >> 16;
-
-			/*we flush the read buffer here*/
-
-			cb_log(9,0,"BCHAN DATA without BC: addr:%x port:%d channel:%d\n",frm->addr, port,channel);
-
-			free_msg(msg);
-			return 1;
-		}
-	}
-
-	return 0;
-}
-
-int manager_isdn_handler(iframe_t *frm ,msg_t *msg)
-{
-
-	if (frm->dinfo==0xffffffff && frm->prim==(PH_DATA|CONFIRM)) {
-		cb_log(0,0,"SERIOUS BUG, dinfo == 0xffffffff, prim == PH_DATA | CONFIRM !!!!\n");
-	}
-
-	/* Timer primitives must be handled first, because the frm->addr is a different
-	 * "address space" than the stack/instance address of other Lx primitives.
-	 */
-	if (handle_timers(msg)) {
-		return 0;
-	}
-
-	if ( ((frm->addr | ISDN_PID_BCHANNEL_BIT )>> 28 ) == 0x5) {
-		static int unhandled_bmsg_count=1000;
-		if (handle_bchan(msg)) {
-			return 0 ;
-		}
-
-		if (unhandled_bmsg_count==1000) {
-			cb_log(0, 0, "received 1k Unhandled Bchannel Messages: prim %x len %d from addr %x, dinfo %x on this port.\n",frm->prim, frm->len, frm->addr, frm->dinfo);
-			unhandled_bmsg_count=0;
-		}
-
-		unhandled_bmsg_count++;
-		free_msg(msg);
-		return 0;
-	}
-
-#ifdef RECV_FRM_SYSLOG_DEBUG
-	syslog(LOG_NOTICE,"mISDN recv: ADDR:%x PRIM:%x DINFO:%x\n", frm->addr, frm->prim, frm->dinfo);
-#endif
-
-	if (handle_mgmt(msg))
-		return 0 ;
-
-	if (handle_l2(msg))
-		return 0 ;
-
-	/* Its important to handle l1 AFTER l2  */
-	if (handle_l1(msg))
-		return 0 ;
-
-	if (handle_frm_nt(msg)) {
-		return 0;
-	}
-
-	if (handle_frm_te(msg)) {
-		return 0;
-	}
-
-	if (handle_err(msg)) {
-		return 0 ;
-	}
-
-	cb_log(0, 0, "Unhandled Message: prim %x len %d from addr %x, dinfo %x on this port.\n",frm->prim, frm->len, frm->addr, frm->dinfo);
-	free_msg(msg);
-
-
-	return 0;
-}
-
-
-
-
-int misdn_lib_get_port_info(int port)
-{
-	msg_t *msg=alloc_msg(MAX_MSG_SIZE);
-	iframe_t *frm;
-	struct misdn_stack *stack=find_stack_by_port(port);
-	if (!msg) {
-		cb_log(0, port, "misdn_lib_get_port_info: alloc_msg failed!\n");
-		return -1;
-	}
-	frm=(iframe_t*)msg->data;
-	if (!stack ) {
-		cb_log(0, port, "There is no Stack for this port.\n");
-		return -1;
-	}
-	/* activate bchannel */
-	frm->prim = CC_STATUS_ENQUIRY | REQUEST;
-
-	frm->addr = stack->upper_id| FLG_MSG_DOWN;
-
-	frm->dinfo = 0;
-	frm->len = 0;
-
-	msg_queue_tail(&glob_mgr->activatequeue, msg);
-	sem_post(&glob_mgr->new_msg);
-
-
-	return 0;
-}
-
-int misdn_lib_pid_restart(int pid)
-{
-	struct misdn_bchannel *bc=manager_find_bc_by_pid(pid);
-
-	if (bc) {
-		manager_clean_bc(bc);
-	}
-	return 0;
-}
-
-/*Sends Restart message for every bchannel*/
-int misdn_lib_send_restart(int port, int channel)
-{
-	struct misdn_stack *stack=find_stack_by_port(port);
-	struct misdn_bchannel dummybc;
-	/*default is all channels*/
-	cb_log(0, port, "Sending Restarts on this port.\n");
-
-	misdn_make_dummy(&dummybc, stack->port, MISDN_ID_GLOBAL, stack->nt, 0);
-
-	/*default is all channels*/
-	if (channel <0) {
-		dummybc.channel=-1;
-		cb_log(0, port, "Restarting and all Interfaces\n");
-		misdn_lib_send_event(&dummybc, EVENT_RESTART);
-
-		return 0;
-	}
-
-	/*if a channel is specified we restart only this one*/
-	if (channel >0) {
-		int cnt;
-		dummybc.channel=channel;
-		cb_log(0, port, "Restarting and cleaning channel %d\n",channel);
-		misdn_lib_send_event(&dummybc, EVENT_RESTART);
-		/* clean up chan in stack, to be sure we don't think it's
-		 * in use anymore */
-		for (cnt=0; cnt<=stack->b_num; cnt++) {
-			if (stack->bc[cnt].in_use && stack->bc[cnt].channel == channel) {
-				empty_bc(&stack->bc[cnt]);
-				clean_up_bc(&stack->bc[cnt]);
-				stack->bc[cnt].in_use=0;
-			}
-		}
-	}
-
-	return 0;
-}
-
-/*reinitializes the L2/L3*/
-int misdn_lib_port_restart(int port)
-{
-	struct misdn_stack *stack=find_stack_by_port(port);
-
-	cb_log(0, port, "Restarting this port.\n");
-	if (stack) {
-		cb_log(0, port, "Stack:%p\n",stack);
-
-		clear_l3(stack);
-		{
-			msg_t *msg=alloc_msg(MAX_MSG_SIZE);
-			iframe_t *frm;
-
-			if (!msg) {
-				cb_log(0, port, "port_restart: alloc_msg failed\n");
-				return -1;
-			}
-
-			frm=(iframe_t*)msg->data;
-			/* we must activate if we are deactivated */
-			/* activate bchannel */
-			frm->prim = DL_RELEASE | REQUEST;
-			frm->addr = stack->upper_id | FLG_MSG_DOWN;
-
-			frm->dinfo = 0;
-			frm->len = 0;
-			msg_queue_tail(&glob_mgr->activatequeue, msg);
-			sem_post(&glob_mgr->new_msg);
-		}
-
-		if (stack->nt)
-			misdn_lib_reinit_nt_stack(stack->port);
-
-	}
-
-	return 0;
-}
-
-
-
-static sem_t handler_started;
-
-/* This is a thread */
-static void manager_event_handler(void *arg)
-{
-	sem_post(&handler_started);
-	while (1) {
-		struct misdn_stack *stack;
-		msg_t *msg;
-
-		/** wait for events **/
-		sem_wait(&glob_mgr->new_msg);
-
-		for (msg=msg_dequeue(&glob_mgr->activatequeue);
-		     msg;
-		     msg=msg_dequeue(&glob_mgr->activatequeue)
-			)
-		{
-
-			iframe_t *frm =  (iframe_t*) msg->data ;
-
-			switch ( frm->prim) {
-			case MGR_SETSTACK | REQUEST :
-				free_msg(msg);
-				break;
-			default:
-				mISDN_write(glob_mgr->midev, frm, mISDN_HEADER_LEN+frm->len, TIMEOUT_1SEC);
-				free_msg(msg);
-			}
-		}
-
-		for (stack=glob_mgr->stack_list;
-		     stack;
-		     stack=stack->next ) {
-			/* Here we should check if we really want to
-				send all the messages we've queued, lets
-				assume we've queued a Disconnect, but
-				received it already from the other side!*/
-
-			while ( (msg=msg_dequeue(&stack->downqueue)) ) {
-				if (stack->nt ) {
-					pthread_mutex_lock(&stack->nstlock);
-					if (stack->nst.manager_l3(&stack->nst, msg))
-						cb_log(0, stack->port, "Error@ Sending Message in NT-Stack.\n");
-					pthread_mutex_unlock(&stack->nstlock);
-				} else {
-					iframe_t *frm = (iframe_t *)msg->data;
-					struct misdn_bchannel *bc = find_bc_by_l3id(stack, frm->dinfo);
-					if (bc)
-						send_msg(glob_mgr->midev, bc, msg);
-					else  {
-						struct misdn_bchannel dummybc;
-
-						misdn_make_dummy(&dummybc, stack->port, frm->dinfo, stack->nt, 0);
-						if (frm->dinfo == MISDN_ID_GLOBAL || frm->dinfo == MISDN_ID_DUMMY ) {
-							cb_log(5,0," --> GLOBAL/DUMMY\n");
-						} else {
-							/*
-							 * We need to be able to at least answer with RELEASE_COMPLETE
-							 * on SETUP|INDICATION errors so use a dummy bc.
-							 */
-							cb_log(0,0,"No bc for Message.  Using dummy_bc\n");
-						}
-						send_msg(glob_mgr->midev, &dummybc, msg);
-					}
-				}
-			}
-		}
-	}
-}
-
-
-int misdn_lib_maxports_get(void)
-{
-	/* BE AWARE WE HAVE NO cb_log() HERE! */
-
-	int i = mISDN_open();
-	int max=0;
-
-	if (i<0)
-		return -1;
-
-	max = mISDN_get_stack_count(i);
-
-	mISDN_close(i);
-
-	return max;
-}
-
-
-void misdn_lib_nt_keepcalls( int kc)
-{
-#ifdef FEATURE_NET_KEEPCALLS
-	if (kc) {
-		struct misdn_stack *stack=get_misdn_stack();
-		for ( ; stack; stack=stack->next) {
-			stack->nst.feature |= FEATURE_NET_KEEPCALLS;
-		}
-	}
-#endif
-}
-
-void misdn_lib_nt_debug_init( int flags, char *file )
-{
-	static int init=0;
-	char *f;
-
-	if (!flags)
-		f=NULL;
-	else
-		f=file;
-
-	if (!init) {
-		debug_init( flags , f, f, f);
-		init=1;
-	} else {
-		debug_close();
-		debug_init( flags , f, f, f);
-	}
-}
-
-int misdn_lib_init(char *portlist, struct misdn_lib_iface *iface, void *user_data)
-{
-	struct misdn_lib *mgr;
-	char *tok, *tokb;
-	char plist[1024];
-	int midev;
-	int port_count=0;
-
-	cb_log = iface->cb_log;
-	cb_event = iface->cb_event;
-	cb_jb_empty = iface->cb_jb_empty;
-
-	if (!portlist || (*portlist == 0)) {
-		return 1;
-	}
-
-	mgr = calloc(1, sizeof(*mgr));
-	if (!mgr) {
-		return 1;
-	}
-	glob_mgr = mgr;
-
-	msg_init();
-
-	misdn_lib_nt_debug_init(0,NULL);
-
-	init_flip_bits();
-
-	strncpy(plist, portlist, 1024);
-	plist[1023] = 0;
-
-	memcpy(tone_425_flip,tone_425,TONE_425_SIZE);
-	flip_buf_bits(tone_425_flip,TONE_425_SIZE);
-
-	memcpy(tone_silence_flip,tone_SILENCE,TONE_SILENCE_SIZE);
-	flip_buf_bits(tone_silence_flip,TONE_SILENCE_SIZE);
-
-	midev=te_lib_init();
-	if (midev <= 0) {
-		free(glob_mgr);
-		glob_mgr = NULL;
-		return 1;
-	}
-	mgr->midev=midev;
-
-	port_count=mISDN_get_stack_count(midev);
-
-	msg_queue_init(&mgr->activatequeue);
-
-	if (sem_init(&mgr->new_msg, 1, 0)<0)
-		sem_init(&mgr->new_msg, 0, 0);
-
-	for (tok=strtok_r(plist," ,",&tokb );
-	     tok;
-	     tok=strtok_r(NULL," ,",&tokb)) {
-		int port = atoi(tok);
-		struct misdn_stack *stack;
-		struct misdn_stack *help;
-		int ptp=0;
-		int i;
-		int r;
-
-		if (strstr(tok, "ptp"))
-			ptp=1;
-
-		if (port > port_count) {
-			cb_log(0, port, "Couldn't Initialize this port since we have only %d ports\n", port_count);
-			continue;
-		}
-
-		stack = stack_init(midev, port, ptp);
-		if (!stack) {
-			cb_log(0, port, "stack_init() failed for this port\n");
-			continue;
-		}
-
-		/* Initialize the B channel records for real B channels. */
-		for (i = 0; i <= stack->b_num; i++) {
-			r = init_bc(stack, &stack->bc[i], stack->midev, port, i);
-			if (r < 0) {
-				cb_log(0, port, "Got Err @ init_bc :%d\n", r);
-				break;
-			}
-		}
-		if (i <= stack->b_num) {
-			stack_destroy(stack);
-			free(stack);
-			continue;
-		}
-#if defined(AST_MISDN_ENHANCEMENTS)
-		/* Initialize the B channel records for REGISTER signaling links. */
-		for (i = MAX_BCHANS + 1; i < ARRAY_LEN(stack->bc); ++i) {
-			r = init_bc(stack, &stack->bc[i], stack->midev, port, i);
-			if (r < 0) {
-				cb_log(0, port, "Got Err @ init_bc :%d\n", r);
-				break;
-			}
-			stack->bc[i].is_register_pool = 1;
-		}
-		if (i < ARRAY_LEN(stack->bc)) {
-			stack_destroy(stack);
-			free(stack);
-			continue;
-		}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-		/* Add the new stack to the end of the list */
-		help = mgr->stack_list;
-		if (!help) {
-			mgr->stack_list = stack;
-		} else {
-			while (help->next) {
-				help = help->next;
-			}
-			help->next = stack;
-		}
-	}
-
-	if (!mgr->stack_list) {
-		/* no stacks were successfully initialized !? */
-		te_lib_destroy(midev);
-		free(glob_mgr);
-		glob_mgr = NULL;
-		return 1;
-	}
-	if (sem_init(&handler_started, 1, 0)<0)
-		sem_init(&handler_started, 0, 0);
-
-	cb_log(8, 0, "Starting Event Handler\n");
-	pthread_create( &mgr->event_handler_thread, NULL,(void*)manager_event_handler, mgr);
-
-	sem_wait(&handler_started) ;
-	cb_log(8, 0, "Starting Event Catcher\n");
-	pthread_create( &mgr->event_thread, NULL, (void*)misdn_lib_isdn_event_catcher, mgr);
-
-	cb_log(8, 0, "Event Catcher started\n");
-
-	global_state= MISDN_INITIALIZED;
-
-	return (mgr == NULL);
-}
-
-void misdn_lib_destroy(void)
-{
-	struct misdn_stack *help;
-	int i;
-
-	for ( help=glob_mgr->stack_list; help; help=help->next ) {
-		for(i=0;i<=help->b_num; i++) {
-			char buf[1024];
-			mISDN_write_frame(help->midev, buf, help->bc[i].addr, MGR_DELLAYER | REQUEST, 0, 0, NULL, TIMEOUT_1SEC);
-			help->bc[i].addr = 0;
-		}
-		cb_log (1, help->port, "Destroying this port.\n");
-		stack_destroy(help);
-	}
-
-	if (global_state == MISDN_INITIALIZED) {
-		cb_log(4, 0, "Killing Handler Thread\n");
-		if ( pthread_cancel(glob_mgr->event_handler_thread) == 0 ) {
-			cb_log(4, 0, "Joining Handler Thread\n");
-			pthread_join(glob_mgr->event_handler_thread, NULL);
-		}
-
-		cb_log(4, 0, "Killing Main Thread\n");
-		if ( pthread_cancel(glob_mgr->event_thread) == 0 ) {
-			cb_log(4, 0, "Joining Main Thread\n");
-			pthread_join(glob_mgr->event_thread, NULL);
-		}
-	}
-
-	cb_log(1, 0, "Closing mISDN device\n");
-	te_lib_destroy(glob_mgr->midev);
-	while ((help = glob_mgr->stack_list)) {
-		glob_mgr->stack_list = help->next;
-		free(help);
-	}
-	free(glob_mgr);
-	glob_mgr = NULL;
-}
-
-char *manager_isdn_get_info(enum event_e event)
-{
-	return isdn_get_info(msgs_g , event, 0);
-}
-
-void manager_bchannel_activate(struct misdn_bchannel *bc)
-{
-	char buf[128];
-
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-
-	if (!stack) {
-		cb_log(0, bc->port, "bchannel_activate: Stack not found !");
-		return ;
-	}
-
-	/* we must activate if we are deactivated */
-	clear_ibuffer(bc->astbuf);
-
-	cb_log(5, stack->port, "$$$ Bchan Activated addr %x\n", bc->addr);
-
-	mISDN_write_frame(stack->midev, buf, bc->addr | FLG_MSG_DOWN,  DL_ESTABLISH | REQUEST, 0,0, NULL, TIMEOUT_1SEC);
-
-	return ;
-}
-
-
-void manager_bchannel_deactivate(struct misdn_bchannel * bc)
-{
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-	char buf[128];
-
-	switch (bc->bc_state) {
-		case BCHAN_ACTIVATED:
-			break;
-		case BCHAN_BRIDGED:
-			misdn_split_conf(bc,bc->conf_id);
-			break;
-		default:
-			cb_log( 4, bc->port,"bchan_deactivate: called but not activated\n");
-			return ;
-
-	}
-
-	cb_log(5, stack->port, "$$$ Bchan deActivated addr %x\n", bc->addr);
-
-	bc->generate_tone=0;
-
-	mISDN_write_frame(stack->midev, buf, bc->addr | FLG_MSG_DOWN, DL_RELEASE|REQUEST,0,0,NULL, TIMEOUT_1SEC);
-
-	clear_ibuffer(bc->astbuf);
-
-	bc_state_change(bc,BCHAN_RELEASE);
-
-	return;
-}
-
-
-int misdn_lib_tx2misdn_frm(struct misdn_bchannel *bc, void *data, int len)
-{
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-	char buf[4096 + mISDN_HEADER_LEN];
-	iframe_t *frm = (iframe_t*)buf;
-
-	switch (bc->bc_state) {
-		case BCHAN_ACTIVATED:
-		case BCHAN_BRIDGED:
-			break;
-		default:
-			cb_log(3, bc->port, "BC not yet activated (state:%s)\n",bc_state2str(bc->bc_state));
-			return -1;
-	}
-
-	frm->prim = DL_DATA|REQUEST;
-	frm->dinfo = 0;
-	frm->addr = bc->addr | FLG_MSG_DOWN ;
-
-	frm->len = len;
-	memcpy(&buf[mISDN_HEADER_LEN], data,len);
-
-	if ( misdn_cap_is_speech(bc->capability) )
-		flip_buf_bits( &buf[mISDN_HEADER_LEN], len);
-	else
-		cb_log(6, stack->port, "Writing %d data bytes\n",len);
-
-	cb_log(9, stack->port, "Writing %d bytes 2 mISDN\n",len);
-	mISDN_write(stack->midev, buf, frm->len + mISDN_HEADER_LEN, TIMEOUT_INFINIT);
-	return 0;
-}
-
-
-
-/*
- * send control information to the channel (dsp-module)
- */
-void manager_ph_control(struct misdn_bchannel *bc, int c1, int c2)
-{
-	unsigned char buffer[mISDN_HEADER_LEN+2*sizeof(int)];
-	iframe_t *ctrl = (iframe_t *)buffer; /* preload data */
-	unsigned int *d = (unsigned int*)&ctrl->data.p;
-	/*struct misdn_stack *stack=get_stack_by_bc(bc);*/
-
-	cb_log(4,bc->port,"ph_control: c1:%x c2:%x\n",c1,c2);
-
-	ctrl->prim = PH_CONTROL | REQUEST;
-	ctrl->addr = bc->addr | FLG_MSG_DOWN;
-	ctrl->dinfo = 0;
-	ctrl->len = sizeof(unsigned int)*2;
-	*d++ = c1;
-	*d++ = c2;
-	mISDN_write(glob_mgr->midev, ctrl, mISDN_HEADER_LEN+ctrl->len, TIMEOUT_1SEC);
-}
-
-/*
- * allow live control of channel parameters
- */
-void isdn_lib_update_rxgain (struct misdn_bchannel *bc)
-{
-	manager_ph_control(bc, VOL_CHANGE_RX, bc->rxgain);
-}
-
-void isdn_lib_update_txgain (struct misdn_bchannel *bc)
-{
-	manager_ph_control(bc, VOL_CHANGE_TX, bc->txgain);
-}
-
-void isdn_lib_update_ec (struct misdn_bchannel *bc)
-{
-#ifdef MISDN_1_2
-	if (*bc->pipeline)
-#else
-	if (bc->ec_enable)
-#endif
-		manager_ec_enable(bc);
-	else
-		manager_ec_disable(bc);
-}
-
-void isdn_lib_stop_dtmf (struct misdn_bchannel *bc)
-{
-	manager_ph_control(bc, DTMF_TONE_STOP, 0);
-}
-
-/*
- * send control information to the channel (dsp-module)
- */
-void manager_ph_control_block(struct misdn_bchannel *bc, int c1, void *c2, int c2_len)
-{
-	unsigned char buffer[mISDN_HEADER_LEN+sizeof(int)+c2_len];
-	iframe_t *ctrl = (iframe_t *)buffer;
-	unsigned int *d = (unsigned int *)&ctrl->data.p;
-	/*struct misdn_stack *stack=get_stack_by_bc(bc);*/
-
-	ctrl->prim = PH_CONTROL | REQUEST;
-	ctrl->addr = bc->addr | FLG_MSG_DOWN;
-	ctrl->dinfo = 0;
-	ctrl->len = sizeof(unsigned int) + c2_len;
-	*d++ = c1;
-	memcpy(d, c2, c2_len);
-	mISDN_write(glob_mgr->midev, ctrl, mISDN_HEADER_LEN+ctrl->len, TIMEOUT_1SEC);
-}
-
-
-
-
-void manager_clean_bc(struct misdn_bchannel *bc )
-{
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-
-	if (stack && bc->channel > 0) {
-		empty_chan_in_stack(stack, bc->channel);
-	}
-	empty_bc(bc);
- 	bc->in_use=0;
-
-	cb_event(EVENT_CLEANUP, bc, NULL);
-}
-
-
-void stack_holder_add(struct misdn_stack *stack, struct misdn_bchannel *holder)
-{
-	struct misdn_bchannel *help;
-	cb_log(4,stack->port, "*HOLDER: add %x\n",holder->l3_id);
-
-	holder->stack_holder=1;
-	holder->next=NULL;
-
-	if (!stack->holding) {
-		stack->holding = holder;
-		return;
-	}
-
-	for (help=stack->holding;
-	     help;
-	     help=help->next) {
-		if (!help->next) {
-			help->next=holder;
-			break;
-		}
-	}
-
-}
-
-void stack_holder_remove(struct misdn_stack *stack, struct misdn_bchannel *holder)
-{
-	struct misdn_bchannel *h1;
-
-	if (!holder->stack_holder) return;
-
-	holder->stack_holder=0;
-
-	cb_log(4,stack->port, "*HOLDER: remove %x\n",holder->l3_id);
-	if (!stack || ! stack->holding) return;
-
-	if (holder == stack->holding) {
-		stack->holding = stack->holding->next;
-		return;
-	}
-
-	for (h1=stack->holding;
-	     h1;
-	     h1=h1->next) {
-		if (h1->next == holder) {
-			h1->next=h1->next->next;
-			return ;
-		}
-	}
-}
-
-struct misdn_bchannel *stack_holder_find(struct misdn_stack *stack, unsigned long l3id)
-{
-	struct misdn_bchannel *help;
-
-	cb_log(4, stack->port, "*HOLDER: find %lx\n",l3id);
-
-	for (help=stack->holding;
-	     help;
-	     help=help->next) {
-		if (help->l3_id == l3id) {
-			cb_log(4,stack->port, "*HOLDER: found bc\n");
-			return help;
-		}
-	}
-
-	cb_log(4,stack->port, "*HOLDER: find nothing\n");
-	return NULL;
-}
-
-/*!
- * \brief Find a held call's B channel record.
- *
- * \param port Port the call is on.
- * \param l3_id mISDN Layer 3 ID of held call.
- *
- * \return Found bc-record or NULL.
- */
-struct misdn_bchannel *misdn_lib_find_held_bc(int port, int l3_id)
-{
-	struct misdn_bchannel *bc;
-	struct misdn_stack *stack;
-
-	bc = NULL;
-	for (stack = get_misdn_stack(); stack; stack = stack->next) {
-		if (stack->port == port) {
-			bc = stack_holder_find(stack, l3_id);
-			break;
-		}
-	}
-
-	return bc;
-}
-
-void misdn_lib_send_tone(struct misdn_bchannel *bc, enum tone_e tone)
-{
-	char buf[mISDN_HEADER_LEN + 128] = "";
-	iframe_t *frm = (iframe_t*)buf;
-
-	switch(tone) {
-	case TONE_DIAL:
-		manager_ph_control(bc, TONE_PATT_ON, TONE_GERMAN_DIALTONE);
-	break;
-
-	case TONE_ALERTING:
-		manager_ph_control(bc, TONE_PATT_ON, TONE_GERMAN_RINGING);
-	break;
-
-	case TONE_HANGUP:
-		manager_ph_control(bc, TONE_PATT_ON, TONE_GERMAN_HANGUP);
-	break;
-
-	case TONE_NONE:
-	default:
-		manager_ph_control(bc, TONE_PATT_OFF, TONE_GERMAN_HANGUP);
-	}
-
-	frm->prim=DL_DATA|REQUEST;
-	frm->addr=bc->addr|FLG_MSG_DOWN;
-	frm->dinfo=0;
-	frm->len=128;
-
-	mISDN_write(glob_mgr->midev, frm, mISDN_HEADER_LEN+frm->len, TIMEOUT_1SEC);
-}
-
-
-void manager_ec_enable(struct misdn_bchannel *bc)
-{
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-
-	cb_log(4, stack?stack->port:0,"ec_enable\n");
-
-	if (!misdn_cap_is_speech(bc->capability)) {
-		cb_log(1, stack?stack->port:0, " --> no speech? cannot enable EC\n");
-	} else {
-
-#ifdef MISDN_1_2
-	if (*bc->pipeline) {
-		cb_log(3, stack?stack->port:0,"Sending Control PIPELINE_CFG %s\n",bc->pipeline);
-		manager_ph_control_block(bc, PIPELINE_CFG, bc->pipeline, strlen(bc->pipeline) + 1);
- 	}
-#else
-	int ec_arr[2];
-
-	if (bc->ec_enable) {
-		cb_log(3, stack?stack->port:0,"Sending Control ECHOCAN_ON taps:%d\n",bc->ec_deftaps);
-
-		switch (bc->ec_deftaps) {
-		case 4:
-		case 8:
-		case 16:
-		case 32:
-		case 64:
-		case 128:
-		case 256:
-		case 512:
-		case 1024:
-			cb_log(4, stack->port, "Taps is %d\n",bc->ec_deftaps);
-			break;
-		default:
-			cb_log(0, stack->port, "Taps should be power of 2\n");
-			bc->ec_deftaps=128;
-		}
-
-		ec_arr[0]=bc->ec_deftaps;
-		ec_arr[1]=0;
-
-		manager_ph_control_block(bc,  ECHOCAN_ON,  ec_arr, sizeof(ec_arr));
-	}
-#endif
-	}
-}
-
-
-
-void manager_ec_disable(struct misdn_bchannel *bc)
-{
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-
-	cb_log(4, stack?stack->port:0," --> ec_disable\n");
-
-	if (!misdn_cap_is_speech(bc->capability)) {
-		cb_log(1, stack?stack->port:0, " --> no speech? cannot disable EC\n");
-		return;
-	}
-
-#ifdef MISDN_1_2
-	manager_ph_control_block(bc, PIPELINE_CFG, "", 0);
-#else
-	if ( ! bc->ec_enable) {
-		cb_log(3, stack?stack->port:0, "Sending Control ECHOCAN_OFF\n");
-		manager_ph_control(bc,  ECHOCAN_OFF, 0);
-	}
-#endif
-}
-
-struct misdn_stack *get_misdn_stack(void)
-{
-	return glob_mgr->stack_list;
-}
-
-
-
-void misdn_join_conf(struct misdn_bchannel *bc, int conf_id)
-{
-	char data[16] = "";
-
-	bc_state_change(bc,BCHAN_BRIDGED);
-	manager_ph_control(bc, CMX_RECEIVE_OFF, 0);
-	manager_ph_control(bc, CMX_CONF_JOIN, conf_id);
-
-	cb_log(3,bc->port, "Joining bc:%x in conf:%d\n",bc->addr,conf_id);
-
-	misdn_lib_tx2misdn_frm(bc, data, sizeof(data) - 1);
-}
-
-
-void misdn_split_conf(struct misdn_bchannel *bc, int conf_id)
-{
-	bc_state_change(bc,BCHAN_ACTIVATED);
-	manager_ph_control(bc, CMX_RECEIVE_ON, 0);
-	manager_ph_control(bc, CMX_CONF_SPLIT, conf_id);
-
-	cb_log(4,bc->port, "Splitting bc:%x in conf:%d\n",bc->addr,conf_id);
-}
-
-void misdn_lib_bridge( struct misdn_bchannel * bc1, struct misdn_bchannel *bc2)
-{
-	int conf_id = bc1->pid + 1;
-	struct misdn_bchannel *bc_list[] = { bc1, bc2, NULL };
-	struct misdn_bchannel **bc;
-
-	cb_log(4, bc1->port, "I Send: BRIDGE from:%d to:%d\n",bc1->port,bc2->port);
-
-	for (bc=bc_list; *bc;  bc++) {
-		(*bc)->conf_id=conf_id;
-		cb_log(4, (*bc)->port, " --> bc_addr:%x\n",(*bc)->addr);
-
-		switch((*bc)->bc_state) {
-			case BCHAN_ACTIVATED:
-				misdn_join_conf(*bc,conf_id);
-				break;
-			default:
-				bc_next_state_change(*bc,BCHAN_BRIDGED);
-				break;
-		}
-	}
-}
-
-void misdn_lib_split_bridge( struct misdn_bchannel * bc1, struct misdn_bchannel *bc2)
-{
-
-	struct misdn_bchannel *bc_list[]={
-		bc1,bc2,NULL
-	};
-	struct misdn_bchannel **bc;
-
-	for (bc=bc_list; *bc;  bc++) {
-		if ( (*bc)->bc_state == BCHAN_BRIDGED){
-			misdn_split_conf( *bc, (*bc)->conf_id);
-		} else {
-			cb_log( 2, (*bc)->port, "BC not bridged (state:%s) so not splitting it\n",bc_state2str((*bc)->bc_state));
-		}
-	}
-
-}
-
-
-
-void misdn_lib_echo(struct misdn_bchannel *bc, int onoff)
-{
-	cb_log(3,bc->port, " --> ECHO %s\n", onoff?"ON":"OFF");
-	manager_ph_control(bc, onoff?CMX_ECHO_ON:CMX_ECHO_OFF, 0);
-}
-
-
-
-void misdn_lib_reinit_nt_stack(int port)
-{
-	struct misdn_stack *stack=find_stack_by_port(port);
-
-	if (stack) {
-		stack->l2link=0;
-		stack->blocked=0;
-
-		cleanup_Isdnl3(&stack->nst);
-		cleanup_Isdnl2(&stack->nst);
-
-
-		memset(&stack->nst, 0, sizeof(net_stack_t));
-		memset(&stack->mgr, 0, sizeof(manager_t));
-
-		stack->mgr.nst = &stack->nst;
-		stack->nst.manager = &stack->mgr;
-
-		stack->nst.l3_manager = handle_event_nt;
-		stack->nst.device = glob_mgr->midev;
-		stack->nst.cardnr = port;
-		stack->nst.d_stid = stack->d_stid;
-
-		stack->nst.feature = FEATURE_NET_HOLD;
-		if (stack->ptp)
-			stack->nst.feature |= FEATURE_NET_PTP;
-		if (stack->pri)
-			stack->nst.feature |= FEATURE_NET_CRLEN2 | FEATURE_NET_EXTCID;
-
-		stack->nst.l1_id = stack->lower_id; /* never used */
-		stack->nst.l2_id = stack->upper_id;
-
-		msg_queue_init(&stack->nst.down_queue);
-
-		Isdnl2Init(&stack->nst);
-		Isdnl3Init(&stack->nst);
-
-		if (!stack->ptp)
-			misdn_lib_get_l1_up(stack);
-	}
-}
diff --git a/channels/misdn/isdn_lib.h b/channels/misdn/isdn_lib.h
deleted file mode 100644
index 62e80d60346ebbad4fb55f58d87a4185526e7bf2..0000000000000000000000000000000000000000
--- a/channels/misdn/isdn_lib.h
+++ /dev/null
@@ -1,833 +0,0 @@
-/*
- * Chan_Misdn -- Channel Driver for Asterisk
- *
- * Interface to mISDN
- *
- * Copyright (C) 2004, Christian Richter
- *
- * Christian Richter <crich@beronet.com>
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License
- */
-
-/*! \file
- * \brief Interface to mISDN
- *
- * \author Christian Richter <crich@beronet.com>
- */
-
-#ifndef TE_LIB
-#define TE_LIB
-
-#include <mISDNuser/suppserv.h>
-
-/** For initialization usage **/
-/* typedef int ie_nothing_t ;*/
-/** end of init usage **/
-
-
-/*
- * uncomment the following to make chan_misdn create
- * record files in /tmp/misdn-{rx|tx}-PortChannel format
- * */
-
-/*#define MISDN_SAVE_DATA*/
-
-#ifdef WITH_BEROEC
-typedef int beroec_t;
-
-
-enum beroec_type {
-	BEROEC_FULLBAND=0,
-	BEROEC_SUBBAND,
-	BEROEC_FASTSUBBAND
-};
-
-void beroec_init(void);
-void beroec_exit(void);
-beroec_t *beroec_new(int tail, enum beroec_type type, int anti_howl,
-		     int tonedisable, int zerocoeff, int adapt, int nlp);
-
-void beroec_destroy(beroec_t *ec);
-int beroec_cancel_alaw_chunk(beroec_t *ec,
-	char *send,
-	char *receive,
-	int len);
-
-int beroec_version(void);
-#endif
-
-
-
-enum tone_e {
-	TONE_NONE=0,
-	TONE_DIAL,
-	TONE_ALERTING,
-	TONE_FAR_ALERTING,
-	TONE_BUSY,
-	TONE_HANGUP,
-	TONE_CUSTOM,
-	TONE_FILE
-};
-
-
-
-#define MAX_BCHANS 31
-
-enum bchannel_state {
-	BCHAN_CLEANED=0,
-	BCHAN_EMPTY,
-	BCHAN_ACTIVATED,
-	BCHAN_BRIDGED,
-	BCHAN_RELEASE,
-	BCHAN_ERROR
-};
-
-
-enum misdn_err_e {
-	ENOCHAN=1
-};
-
-enum mISDN_NUMBER_PLAN {
-	NUMPLAN_UNKNOWN = 0x0,
-	NUMPLAN_ISDN = 0x1,		/* ISDN/Telephony numbering plan E.164 */
-	NUMPLAN_DATA = 0x3,		/* Data numbering plan X.121 */
-	NUMPLAN_TELEX = 0x4,	/* Telex numbering plan F.69 */
-	NUMPLAN_NATIONAL = 0x8,
-	NUMPLAN_PRIVATE = 0x9
-};
-
-enum mISDN_NUMBER_TYPE {
-	NUMTYPE_UNKNOWN = 0x0,
-	NUMTYPE_INTERNATIONAL = 0x1,
-	NUMTYPE_NATIONAL = 0x2,
-	NUMTYPE_NETWORK_SPECIFIC = 0x3,
-	NUMTYPE_SUBSCRIBER = 0x4,
-	NUMTYPE_ABBREVIATED = 0x5
-};
-
-enum event_response_e {
-	RESPONSE_IGNORE_SETUP_WITHOUT_CLOSE,
-	RESPONSE_IGNORE_SETUP,
-	RESPONSE_RELEASE_SETUP,
-	RESPONSE_ERR,
-	RESPONSE_OK
-};
-
-
-enum event_e {
-	EVENT_NOTHING,
-	EVENT_TONE_GENERATE,
-	EVENT_BCHAN_DATA,
-	EVENT_BCHAN_ACTIVATED,
-	EVENT_BCHAN_ERROR,
-	EVENT_CLEANUP,
-	EVENT_PROCEEDING,
-	EVENT_PROGRESS,
-	EVENT_SETUP,
-	EVENT_REGISTER,
-	EVENT_ALERTING,
-	EVENT_CONNECT,
-	EVENT_SETUP_ACKNOWLEDGE,
-	EVENT_CONNECT_ACKNOWLEDGE ,
-	EVENT_USER_INFORMATION,
-	EVENT_SUSPEND_REJECT,
-	EVENT_RESUME_REJECT,
-	EVENT_HOLD,
-	EVENT_SUSPEND,
-	EVENT_RESUME,
-	EVENT_HOLD_ACKNOWLEDGE,
-	EVENT_SUSPEND_ACKNOWLEDGE,
-	EVENT_RESUME_ACKNOWLEDGE,
-	EVENT_HOLD_REJECT,
-	EVENT_RETRIEVE,
-	EVENT_RETRIEVE_ACKNOWLEDGE,
-	EVENT_RETRIEVE_REJECT,
-	EVENT_DISCONNECT,
-	EVENT_RESTART,
-	EVENT_RELEASE,
-	EVENT_RELEASE_COMPLETE,
-	EVENT_FACILITY,
-	EVENT_NOTIFY,
-	EVENT_STATUS_ENQUIRY,
-	EVENT_INFORMATION,
-	EVENT_STATUS,
-	EVENT_TIMEOUT,
-	EVENT_DTMF_TONE,
-	EVENT_NEW_L3ID,
-	EVENT_NEW_BC,
-	EVENT_PORT_ALARM,
-	EVENT_NEW_CHANNEL,
-	EVENT_UNKNOWN
-};
-
-
-enum ie_name_e {
-	IE_DUMMY,
-	IE_LAST
-};
-
-enum { /* bearer capability */
-	INFO_CAPABILITY_SPEECH=0,
-	INFO_CAPABILITY_AUDIO_3_1K=0x10 ,
-	INFO_CAPABILITY_AUDIO_7K=0x11 ,
-	INFO_CAPABILITY_VIDEO =0x18,
-	INFO_CAPABILITY_DIGITAL_UNRESTRICTED =0x8,
-	INFO_CAPABILITY_DIGITAL_RESTRICTED =0x09,
-	INFO_CAPABILITY_DIGITAL_UNRESTRICTED_TONES
-};
-
-enum { /* progress indicators */
-	INFO_PI_CALL_NOT_E2E_ISDN =0x01,
-	INFO_PI_CALLED_NOT_ISDN =0x02,
-	INFO_PI_CALLER_NOT_ISDN =0x03,
-	INFO_PI_CALLER_RETURNED_TO_ISDN =0x04,
-	INFO_PI_INBAND_AVAILABLE =0x08,
-	INFO_PI_DELAY_AT_INTERF =0x0a,
-	INFO_PI_INTERWORKING_WITH_PUBLIC =0x10,
-	INFO_PI_INTERWORKING_NO_RELEASE =0x11,
-	INFO_PI_INTERWORKING_NO_RELEASE_PRE_ANSWER =0x12,
-	INFO_PI_INTERWORKING_NO_RELEASE_POST_ANSWER =0x13
-};
-
-/*!
- * \brief Q.931 encoded redirecting reason
- */
-enum mISDN_REDIRECTING_REASON {
-	mISDN_REDIRECTING_REASON_UNKNOWN = 0x0,
-	/*! Call forwarding busy or called DTE busy */
-	mISDN_REDIRECTING_REASON_CALL_FWD_BUSY = 0x1,
-	/*! Call forwarding no reply */
-	mISDN_REDIRECTING_REASON_NO_REPLY = 0x2,
-	/*! Call deflection */
-	mISDN_REDIRECTING_REASON_DEFLECTION = 0x4,
-	/*! Called DTE out of order */
-	mISDN_REDIRECTING_REASON_OUT_OF_ORDER = 0x9,
-	/*! Call forwarding by the called DTE */
-	mISDN_REDIRECTING_REASON_CALL_FWD_DTE = 0xA,
-	/*! Call forwarding unconditional or systematic call redirection */
-	mISDN_REDIRECTING_REASON_CALL_FWD = 0xF
-};
-
-/*!
- * \brief Notification description code enumeration
- */
-enum mISDN_NOTIFY_CODE {
-	mISDN_NOTIFY_CODE_INVALID = -1,
-	/*! Call is placed on hold (Q.931) */
-	mISDN_NOTIFY_CODE_USER_SUSPEND = 0x00,
-	/*! Call is taken off of hold (Q.931) */
-	mISDN_NOTIFY_CODE_USER_RESUME = 0x01,
-	/*! Call is diverting (EN 300 207-1 Section 7.2.1) */
-	mISDN_NOTIFY_CODE_CALL_IS_DIVERTING = 0x7B,
-	/*! Call diversion is enabled (cfu, cfb, cfnr) (EN 300 207-1 Section 7.2.1) */
-	mISDN_NOTIFY_CODE_DIVERSION_ACTIVATED = 0x68,
-	/*! Call transfer, alerting (EN 300 369-1 Section 7.2) */
-	mISDN_NOTIFY_CODE_CALL_TRANSFER_ALERTING = 0x69,
-	/*! Call transfer, active(answered) (EN 300 369-1 Section 7.2) */
-	mISDN_NOTIFY_CODE_CALL_TRANSFER_ACTIVE = 0x6A,
-};
-
-enum { /*CODECS*/
-	INFO_CODEC_ULAW=2,
-	INFO_CODEC_ALAW=3
-};
-
-
-enum layer_e {
-	L3,
-	L2,
-	L1,
-	UNKNOWN
-};
-
-/*! Maximum phone number (address) length plus null terminator */
-#define MISDN_MAX_NUMBER_LEN		(31 + 1)
-
-/*! Maximum name length plus null terminator (From ECMA-164) */
-#define MISDN_MAX_NAME_LEN			(50 + 1)
-
-/*! Maximum subaddress length plus null terminator */
-#define MISDN_MAX_SUBADDRESS_LEN	(23 + 1)
-
-/*! Maximum keypad facility content length plus null terminator */
-#define MISDN_MAX_KEYPAD_LEN		(31 + 1)
-
-/*! \brief Dialed/Called information struct */
-struct misdn_party_dialing {
-	/*! \brief Type-of-number in ISDN terms for the dialed/called number */
-	enum mISDN_NUMBER_TYPE number_type;
-
-	/*! \brief Type-of-number numbering plan. */
-	enum mISDN_NUMBER_PLAN number_plan;
-
-	/*! \brief Dialed/Called Phone Number (Address) */
-	char number[MISDN_MAX_NUMBER_LEN];
-
-	/*! \brief Dialed/Called Subaddress number */
-	char subaddress[MISDN_MAX_SUBADDRESS_LEN];
-};
-
-/*! \brief Connected-Line/Calling/Redirecting ID info struct */
-struct misdn_party_id {
-	/*! \brief Number presentation restriction code
-	 * 0=Allowed, 1=Restricted, 2=Unavailable
-	 */
-	int presentation;
-
-	/*! \brief Number screening code
-	 * 0=Unscreened, 1=Passed Screen, 2=Failed Screen, 3=Network Number
-	 */
-	int screening;
-
-	/*! \brief Type-of-number in ISDN terms for the number */
-	enum mISDN_NUMBER_TYPE number_type;
-
-	/*! \brief Type-of-number numbering plan. */
-	enum mISDN_NUMBER_PLAN number_plan;
-
-	/*! \brief Subscriber Name
-	 * \note The name is currently obtained from Asterisk for
-	 * potential use in display ie's since basic ISDN does
-	 * not support names directly.
-	 */
-	char name[MISDN_MAX_NAME_LEN];
-
-	/*! \brief Phone number (Address) */
-	char number[MISDN_MAX_NUMBER_LEN];
-
-	/*! \brief Subaddress number */
-	char subaddress[MISDN_MAX_SUBADDRESS_LEN];
-};
-
-/*! \brief Redirecting information struct */
-struct misdn_party_redirecting {
-	/*! \brief Who is redirecting the call (Sent to the party the call is redirected toward) */
-	struct misdn_party_id from;
-
-	/*! \brief Where the call is being redirected toward (Sent to the calling party) */
-	struct misdn_party_id to;
-
-	/*! \brief Reason a call is being redirected (Q.931 field value) */
-	enum mISDN_REDIRECTING_REASON reason;
-
-	/*! \brief Number of times the call has been redirected */
-	int count;
-
-	/*! \brief TRUE if the redirecting.to information has changed */
-	int to_changed;
-};
-
-
-/*! \brief B channel control structure */
-struct misdn_bchannel {
-	/*! \brief B channel send locking structure */
-	struct send_lock *send_lock;
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	/*! \brief The BC, HLC (optional) and LLC (optional) contents from the SETUP message. */
-	struct Q931_Bc_Hlc_Llc setup_bc_hlc_llc;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-	/*!
-	 * \brief Dialed/Called information struct
-	 * \note The number_type element is set to "dialplan" in /etc/asterisk/misdn.conf for outgoing calls
-	 */
-	struct misdn_party_dialing dialed;
-
-	/*! \brief Originating/Caller ID information struct
-	 * \note The number_type element can be set to "localdialplan" in /etc/asterisk/misdn.conf for outgoing calls
-	 * \note The number element can be set to "callerid" in /etc/asterisk/misdn.conf for outgoing calls
-	 */
-	struct misdn_party_id caller;
-
-	/*! \brief  Incoming Caller ID string tag for special purpose
-	 * \note The element can be set to "incoming_cid_tag" in /etc/asterisk/misdn.conf for incoming calls
-	 */
-	char incoming_cid_tag[MISDN_MAX_NAME_LEN];
-
-	/*! \brief Connected-Party/Connected-Line ID information struct
-	 * \note The number_type element can be set to "cpndialplan" in /etc/asterisk/misdn.conf for outgoing calls
-	 */
-	struct misdn_party_id connected;
-
-	/*! \brief Redirecting information struct (Where a call diversion or transfer was invoked)
-	 * \note The redirecting subaddress is not defined in Q.931 so it is not used.
-	 */
-	struct misdn_party_redirecting redirecting;
-
-	/*! \brief TRUE if this is a dummy BC record */
-	int dummy;
-
-	/*! \brief TRUE if NT side of protocol (TE otherwise) */
-	int nt;
-
-	/*! \brief TRUE if ISDN-PRI (ISDN-BRI otherwise) */
-	int pri;
-
-	/*! \brief Logical Layer 1 port associated with this B channel */
-	int port;
-
-	/** init stuff **/
-	/*! \brief B Channel mISDN driver stack ID */
-	int b_stid;
-
-	/* int b_addr; */
-
-	/*! \brief B Channel mISDN driver layer ID from mISDN_new_layer() */
-	int layer_id;
-
-	/*! \brief B channel layer; set to 3 or 4 */
-	int layer;
-
-	/* state stuff */
-	/*! \brief TRUE if DISCONNECT needs to be sent to clear a call */
-	int need_disconnect;
-
-	/*! \brief TRUE if RELEASE needs to be sent to clear a call */
-	int need_release;
-
-	/*! \brief TRUE if RELEASE_COMPLETE needs to be sent to clear a call */
-	int need_release_complete;
-
-	/*! \brief TRUE if allocate higher B channels first */
-	int dec;
-
-	/* var stuff */
-	/*! \brief Layer 3 process ID */
-	int l3_id;
-
-	/*! \brief B channel process ID (1-5000) */
-	int pid;
-
-	/*! \brief Not used. Saved mISDN stack CONNECT_t ces value */
-	int ces;
-
-	/*! \brief B channel to restart if received a RESTART message */
-	int restart_channel;
-
-	/*! \brief Assigned B channel number B1, B2... 0 if not assigned */
-	int channel;
-
-	/*! \brief TRUE if the B channel number is preselected */
-	int channel_preselected;
-
-	/*! \brief TRUE if the B channel is allocated from the REGISTER pool */
-	int is_register_pool;
-
-	/*! \brief TRUE if B channel record is in use */
-	int in_use;
-
-	/*! \brief Time when empty_bc() last called on this record */
-	struct timeval last_used;
-
-	/*! \brief TRUE if call waiting */
-	int cw;
-
-	/*! \brief B Channel mISDN driver layer ID from mISDN_get_layerid() */
-	int addr;
-
-	/*! \brief B channel speech sample data buffer */
-	char *bframe;
-
-	/*! \brief B channel speech sample data buffer size */
-	int bframe_len;
-	int time_usec;	/* Not used */
-
-	/*! \brief Not used. Contents are setup but not used. */
-	void *astbuf;
-
-	/*! \brief TRUE if the TE side should choose the B channel to use
-	 * \note This value is user configurable in /etc/asterisk/misdn.conf
-	 */
-	int te_choose_channel;
-
-	/*! \brief TRUE if the call progress indicators can indicate an inband audio message for the user to listen to
-	 * \note This value is user configurable in /etc/asterisk/misdn.conf
-	 */
-	int early_bconnect;
-
-	/*! \brief Last decoded DTMF digit from mISDN driver */
-	int dtmf;
-
-	/*! \brief TRUE if we should produce DTMF tones ourselves
-	 * \note This value is user configurable in /etc/asterisk/misdn.conf
-	 */
-	int send_dtmf;
-
-	/*! \brief TRUE if we send SETUP_ACKNOWLEDGE on incoming calls anyway (instead of PROCEEDING).
-	 *
-	 * This requests additional INFORMATION messages, so we can
-	 * wait for digits without issues.
-	 * \note This value is user configurable in /etc/asterisk/misdn.conf
-	 */
-	int need_more_infos;
-
-	/*! \brief TRUE if all digits necessary to complete the call are available.
-	 * No more INFORMATION messages are needed.
-	 */
-	int sending_complete;
-
-
-	/*! \brief TRUE if we will not use jollys dsp */
-	int nodsp;
-
-	/*! \brief TRUE if we will not use the jitter buffer system */
-	int nojitter;
-
-	/*! \brief Progress Indicator IE coding standard field.
-	 * \note Collected from the incoming messages but not used.
-	 */
-	int progress_coding;
-
-	/*! \brief Progress Indicator IE location field.
-	 * \note Collected from the incoming messages but not used.
-	 */
-	int progress_location;
-
-	/*! \brief Progress Indicator IE progress description field.
-	 * Used to determine if there is an inband audio message present.
-	 */
-	int progress_indicator;
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	/*!
-	 * \brief TRUE if waiting for DivertingLegInformation3 to queue redirecting update.
-	 */
-	int div_leg_3_rx_wanted;
-
-	/*!
-	 * \brief TRUE if a DivertingLegInformation3 needs to be sent with CONNECT.
-	 */
-	int div_leg_3_tx_pending;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-	/*! \brief Inbound FACILITY message function type and contents */
-	struct FacParm fac_in;
-
-	/*! \brief Outbound FACILITY message function type and contents.
-	 * \note Filled in by misdn facility commands before FACILITY message sent.
-	 */
-	struct FacParm fac_out;
-
-	/* storing the current AOCD info here */
-	enum FacFunction AOCDtype;
-	union {
-		struct FacAOCDCurrency currency;
-		struct FacAOCDChargingUnit chargingUnit;
-	} AOCD;
-	/*! \brief TRUE if AOCDtype and AOCD data are ready to export to Asterisk */
-	int AOCD_need_export;
-
-	/*** CRYPTING STUFF ***/
-	int crypt;		/* Initialized, Not used */
-	int curprx;		/* Initialized, Not used */
-	int curptx;		/* Initialized, Not used */
-
-	/*! \brief Blowfish encryption key string (secret) */
-	char crypt_key[255];
-
-	int crypt_state;	/* Not used */
-	/*** CRYPTING STUFF END***/
-
-	/*! \brief Seems to have been intended for something to do with the jitter buffer.
-	 * \note Used as a boolean.  Only initialized to 0 and referenced in a couple places
-	 */
-	int active;
-	int upset;	/* Not used */
-
-	/*! \brief TRUE if tone generator allowed to start */
-	int generate_tone;
-
-	/*! \brief Number of tone samples to generate */
-	int tone_cnt;
-
-	/*! \brief Current B Channel state */
-	enum bchannel_state bc_state;
-
-	/*! \brief This is used as a pending bridge join request for when bc_state becomes BCHAN_ACTIVATED */
-	enum bchannel_state next_bc_state;
-
-	/*! \brief Bridging conference ID */
-	int conf_id;
-
-	/*! \brief TRUE if this channel is on hold */
-	int holded;
-
-	/*! \brief TRUE if this channel is on the misdn_stack->holding list
-	 * \note If TRUE this implies that the structure is also malloced.
-	 */
-	int stack_holder;
-
-	/*!
-	 * \brief Put a display ie in the CONNECT message
-	 * \details
-	 * Put a display ie in the CONNECT message containing the following
-	 * information if it is available (nt port only):
-	 * 0 - Do not put the connected line information in the display ie.
-	 * 1 - Put the available connected line name in the display ie.
-	 * 2 - Put the available connected line number in the display ie.
-	 * 3 - Put the available connected line name and number in the display ie.
-	 */
-	int display_connected;
-
-	/*!
-	 * \brief Put a display ie in the SETUP message
-	 * \details
-	 * Put a display ie in the SETUP message containing the following
-	 * information if it is available (nt port only):
-	 * 0 - Do not put the caller information in the display ie.
-	 * 1 - Put the available caller name in the display ie.
-	 * 2 - Put the available caller number in the display ie.
-	 * 3 - Put the available caller name and number in the display ie.
-	 */
-	int display_setup;
-
-	/*!
-	 * \brief Select what to do with outgoing COLP information.
-	 * \details
-	 * 0 - pass (Send out COLP information unaltered.)
-	 * 1 - restricted (Force COLP to restricted on all outgoing COLP information.)
-	 * 2 - block (Do not send COLP information.)
-	 */
-	int outgoing_colp;
-
-	/*! \brief User set presentation restriction code
-	 * 0=Allowed, 1=Restricted, 2=Unavailable
-	 * \note It is settable by the misdn_set_opt() application.
-	 */
-	int presentation;
-
-	/*! \brief TRUE if the user set the presentation restriction code */
-	int set_presentation;
-
-	/*! \brief Notification indicator ie description code */
-	enum mISDN_NOTIFY_CODE notify_description_code;
-
-	/*! \brief SETUP message bearer capability field code value */
-	int capability;
-
-	/*! \brief Companding ALaw/uLaw encoding (INFO_CODEC_ALAW / INFO_CODEC_ULAW) */
-	int law;
-
-	/* V110 Stuff */
-	/*! \brief Q.931 Bearer Capability IE Information Transfer Rate field. Initialized to 0x10 (64kbit). Altered by incoming SETUP messages. */
-	int rate;
-
-	/*! \brief Q.931 Bearer Capability IE Transfer Mode field. Initialized to 0 (Circuit). Altered by incoming SETUP messages. */
-	int mode;
-
-	/*! \brief Q.931 Bearer Capability IE User Information Layer 1 Protocol field code.
-	 * \note Collected from the incoming SETUP message but not used.
-	 */
-	int user1;
-
-	/*! \brief Q.931 Bearer Capability IE Layer 1 User Rate field.
-	 * \note Collected from the incoming SETUP message and exported to Asterisk variable MISDN_URATE.
-	 */
-	int urate;
-
-	/*! \brief TRUE if call made in digital HDLC mode
-	 * \note This value is user configurable in /etc/asterisk/misdn.conf.
-	 * It is also settable by the misdn_set_opt() application.
-	 */
-	int hdlc;
-	/* V110 */
-
-	/*! \brief Display message that can be displayed by the user phone.
-	 * \note Maximum displayable length is 34 or 82 octets.
-	 * It is also settable by the misdn_set_opt() application.
-	 */
-	char display[84];
-
-	/*! \brief Q.931 Keypad Facility IE contents
-	 * \note Contents exported and imported to Asterisk variable MISDN_KEYPAD
-	 */
-	char keypad[MISDN_MAX_KEYPAD_LEN];
-
-	/*! \brief Current overlap dialing digits to/from INFORMATION messages */
-	char info_dad[MISDN_MAX_NUMBER_LEN];
-
-	/*! \brief Collected digits to go into info_dad[] while waiting for a SETUP_ACKNOWLEDGE to come in. */
-	char infos_pending[MISDN_MAX_NUMBER_LEN];
-
-/* 	unsigned char info_keypad[MISDN_MAX_KEYPAD_LEN]; */
-/* 	unsigned char clisub[24]; */
-/* 	unsigned char cldsub[24]; */
-
-	/*! \brief User-User information string.
-	 * \note Contents exported and imported to Asterisk variable MISDN_USERUSER
-	 * \note We only support ASCII strings (IA5 characters).
-	 */
- 	char uu[256];
-
-	/*! \brief User-User information string length in uu[] */
-	int uulen;
-
-	/*! \brief Q.931 Cause for disconnection code (received)
-	 * \note Need to use the AST_CAUSE_xxx code definitions in causes.h
-	 */
-	int cause;
-
-	/*! \brief Q.931 Cause for disconnection code (sent)
-	 * \note Need to use the AST_CAUSE_xxx code definitions in causes.h
-	 * \note -1 is used to suppress including the cause code in the RELEASE message.
-	 */
-	int out_cause;
-
-	/* struct misdn_bchannel hold_bc; */
-
-	/** list stuf **/
-
-#ifdef MISDN_1_2
-	/*! \brief The configuration string for the mISDN dsp pipeline in /etc/asterisk/misdn.conf. */
-	char pipeline[128];
-#else
-	/*! \brief TRUE if the echo cancellor is enabled */
-	int ec_enable;
-
-	/*! \brief Number of taps in the echo cancellor when enabled.
-	 * \note This value is user configurable in /etc/asterisk/misdn.conf (echocancel)
-	 */
-	int ec_deftaps;
-#endif
-
-	/*! \brief TRUE if the channel was allocated from the available B channels */
-	int channel_found;
-
-	/*! \brief Who originated the call (ORG_AST, ORG_MISDN)
-	 * \note Set but not used when the misdn_set_opt() application enables echo cancellation.
-	 */
-	int orig;
-
-	/*! \brief Tx gain setting (range -8 to 8)
-	 * \note This value is user configurable in /etc/asterisk/misdn.conf.
-	 * It is also settable by the misdn_set_opt() application.
-	 */
-	int txgain;
-
-	/*! \brief Rx gain setting (range -8 to 8)
-	 * \note This value is user configurable in /etc/asterisk/misdn.conf.
-	 * It is also settable by the misdn_set_opt() application.
-	 */
-	int rxgain;
-
-	/*! \brief Next node in the misdn_stack.holding list */
-	struct misdn_bchannel *next;
-};
-
-
-extern enum event_response_e (*cb_event) (enum event_e event, struct misdn_bchannel *bc, void *user_data);
-
-extern void (*cb_log) (int level, int port, char *tmpl, ...)
-	__attribute__ ((format (printf, 3, 4)));
-
-extern int (*cb_jb_empty)(struct misdn_bchannel *bc, char *buffer, int len);
-
-struct misdn_lib_iface {
-	enum event_response_e (*cb_event)(enum event_e event, struct misdn_bchannel *bc, void *user_data);
-	void (*cb_log)(int level, int port, char *tmpl, ...)
-		__attribute__ ((format (printf, 3, 4)));
-	int (*cb_jb_empty)(struct misdn_bchannel *bc, char *buffer, int len);
-};
-
-/***** USER IFACE **********/
-
-void misdn_lib_nt_keepcalls(int kc);
-
-void misdn_lib_nt_debug_init( int flags, char *file );
-
-int misdn_lib_init(char *portlist, struct misdn_lib_iface* iface, void *user_data);
-int misdn_lib_send_event(struct misdn_bchannel *bc, enum event_e event );
-void misdn_lib_destroy(void);
-
-void misdn_lib_isdn_l1watcher(int port);
-
-void misdn_lib_log_ies(struct misdn_bchannel *bc);
-
-char *manager_isdn_get_info(enum event_e event);
-
-struct misdn_bchannel* misdn_lib_get_free_bc(int port, int channel, int inout, int dec);
-#if defined(AST_MISDN_ENHANCEMENTS)
-struct misdn_bchannel *misdn_lib_get_register_bc(int port);
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-void manager_bchannel_activate(struct misdn_bchannel *bc);
-void manager_bchannel_deactivate(struct misdn_bchannel * bc);
-
-int misdn_lib_tx2misdn_frm(struct misdn_bchannel *bc, void *data, int len);
-
-void manager_ph_control(struct misdn_bchannel *bc, int c1, int c2);
-
-void isdn_lib_update_rxgain (struct misdn_bchannel *bc);
-void isdn_lib_update_txgain (struct misdn_bchannel *bc);
-void isdn_lib_update_ec (struct misdn_bchannel *bc);
-void isdn_lib_stop_dtmf (struct misdn_bchannel *bc);
-
-int misdn_lib_port_restart(int port);
-int misdn_lib_pid_restart(int pid);
-int misdn_lib_send_restart(int port, int channel);
-
-int misdn_lib_get_port_info(int port);
-
-int misdn_lib_is_port_blocked(int port);
-int misdn_lib_port_block(int port);
-int misdn_lib_port_unblock(int port);
-
-int misdn_lib_port_is_pri(int port);
-int misdn_lib_port_is_nt(int port);
-
-int misdn_lib_port_up(int port, int notcheck);
-
-int misdn_lib_get_port_down(int port);
-
-int misdn_lib_get_port_up (int port) ;
-
-int misdn_lib_maxports_get(void) ;
-
-struct misdn_bchannel *misdn_lib_find_held_bc(int port, int l3_id);
-void misdn_lib_release(struct misdn_bchannel *bc);
-
-int misdn_cap_is_speech(int cap);
-int misdn_inband_avail(struct misdn_bchannel *bc);
-
-void manager_ec_enable(struct misdn_bchannel *bc);
-void manager_ec_disable(struct misdn_bchannel *bc);
-
-void misdn_lib_send_tone(struct misdn_bchannel *bc, enum tone_e tone);
-
-void get_show_stack_details(int port, char *buf);
-
-
-void misdn_lib_tone_generator_start(struct misdn_bchannel *bc);
-void misdn_lib_tone_generator_stop(struct misdn_bchannel *bc);
-
-
-void misdn_lib_bridge( struct misdn_bchannel * bc1, struct misdn_bchannel *bc2);
-void misdn_lib_split_bridge( struct misdn_bchannel * bc1, struct misdn_bchannel *bc2);
-
-void misdn_lib_echo(struct misdn_bchannel *bc, int onoff);
-
-int misdn_lib_is_ptp(int port);
-int misdn_lib_get_maxchans(int port);
-
-void misdn_lib_reinit_nt_stack(int port);
-
-#define PRI_TRANS_CAP_SPEECH                                    0x0
-#define PRI_TRANS_CAP_DIGITAL                                   0x08
-#define PRI_TRANS_CAP_RESTRICTED_DIGITAL                        0x09
-#define PRI_TRANS_CAP_3_1K_AUDIO                                0x10
-#define PRI_TRANS_CAP_7K_AUDIO                                  0x11
-
-
-
-char *bc_state2str(enum bchannel_state state);
-void bc_state_change(struct misdn_bchannel *bc, enum bchannel_state state);
-
-void misdn_dump_chanlist(void);
-
-void misdn_make_dummy(struct misdn_bchannel *dummybc, int port, int l3id, int nt, int channel);
-
-
-#endif	/* TE_LIB */
diff --git a/channels/misdn/isdn_lib_intern.h b/channels/misdn/isdn_lib_intern.h
deleted file mode 100644
index d71fe77532605ca394361c32425143992317d4d0..0000000000000000000000000000000000000000
--- a/channels/misdn/isdn_lib_intern.h
+++ /dev/null
@@ -1,159 +0,0 @@
-#ifndef ISDN_LIB_INTERN
-#define ISDN_LIB_INTERN
-
-
-#include <mISDNuser/mISDNlib.h>
-#include <mISDNuser/isdn_net.h>
-#include <mISDNuser/l3dss1.h>
-#include <mISDNuser/net_l3.h>
-
-#include <pthread.h>
-
-#include "isdn_lib.h"
-
-#ifndef MISDNUSER_VERSION_CODE
-#error "You need a newer version of mISDNuser ..."
-#elif MISDNUSER_VERSION_CODE < MISDNUSER_VERSION(1, 0, 3)
-#error "You need a newer version of mISDNuser ..."
-#endif
-
-
-#define QI_ELEMENT(a) a.off
-
-
-#ifndef mISDNUSER_HEAD_SIZE
-
-#define mISDNUSER_HEAD_SIZE (sizeof(mISDNuser_head_t))
-/*#define mISDNUSER_HEAD_SIZE (sizeof(mISDN_head_t))*/
-#endif
-
-
-#if 0
-ibuffer_t *astbuf;		/* Not used */
-ibuffer_t *misdnbuf;	/* Not used */
-#endif
-
-struct send_lock {
-	pthread_mutex_t lock;
-};
-
-
-struct isdn_msg {
-	unsigned long misdn_msg;
-
-	enum event_e event;
-
-	void (*msg_parser)(struct isdn_msg *msgs, msg_t *msg, struct misdn_bchannel *bc, int nt);
-	msg_t *(*msg_builder)(struct isdn_msg *msgs, struct misdn_bchannel *bc, int nt);
-	char *info;
-} ;
-
-/* for isdn_msg_parser.c */
-msg_t *create_l3msg(int prim, int mt, int dinfo , int size, int nt);
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/* Max call-completion REGISTER signaling links per stack/port */
-#define MISDN_MAX_REGISTER_LINKS	MAX_BCHANS
-#else
-/* Max call-completion REGISTER signaling links per stack/port */
-#define MISDN_MAX_REGISTER_LINKS	0
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#define MAXPROCS 0x100
-
-struct misdn_stack {
-	/** is first element because &nst equals &mISDNlist **/
-	net_stack_t nst;
-	manager_t mgr;
-	pthread_mutex_t nstlock;
-
-	/*! \brief Stack struct critical section lock. */
-	pthread_mutex_t st_lock;
-
-	/*! \brief D Channel mISDN driver stack ID (Parent stack ID) */
-	int d_stid;
-
-	/*! \brief Number of B channels supported by this port */
-	int b_num;
-
-	/*! \brief B Channel mISDN driver stack IDs (Child stack IDs) */
-	int b_stids[MAX_BCHANS + 1];
-
-	/*! \brief TRUE if Point-To-Point(PTP) (Point-To-Multipoint(PTMP) otherwise) */
-	int ptp;
-
-	/*! \brief Number of consecutive times PTP Layer 2 declared down */
-	int l2upcnt;
-
-	int l2_id;	/* Not used */
-
-	/*! \brief Lower layer mISDN ID (addr) (Layer 1/3) */
-	int lower_id;
-
-	/*! \brief Upper layer mISDN ID (addr) (Layer 2/4) */
-	int upper_id;
-
-	/*! \brief TRUE if port is blocked */
-  	int blocked;
-
-	/*! \brief TRUE if Layer 2 is UP */
-	int l2link;
-
-	/*! \brief TRUE if Layer 1 is UP */
-	int l1link;
-
-	/*! \brief TRUE if restart has been sent to the other side after stack startup */
-	int restart_sent;
-
-	/*! \brief mISDN device handle returned by mISDN_open() */
-	int midev;
-
-	/*! \brief TRUE if NT side of protocol (TE otherwise) */
-	int nt;
-
-	/*! \brief TRUE if ISDN-PRI (ISDN-BRI otherwise) */
-	int pri;
-
-	/*! \brief CR Process ID allocation table.  TRUE if ID allocated */
-	int procids[MAXPROCS];
-
-	/*! \brief Queue of Event messages to send to mISDN */
-	msg_queue_t downqueue;
-	msg_queue_t upqueue;	/* No code puts anything on this queue */
-	int busy;	/* Not used */
-
-	/*! \brief Logical Layer 1 port associated with this stack */
-	int port;
-
-	/*!
-	 * \brief B Channel record pool array
-	 * (Must be dimensioned the same as struct misdn_stack.channels[])
-	 */
-	struct misdn_bchannel bc[MAX_BCHANS + 1 + MISDN_MAX_REGISTER_LINKS];
-
-	/*!
-	 * \brief Array of B channels in use (a[0] = B1).  TRUE if B channel in use.
-	 * (Must be dimensioned the same as struct misdn_stack.bc[])
-	 */
-	char channels[MAX_BCHANS + 1 + MISDN_MAX_REGISTER_LINKS];
-
-	/*! \brief List of held channels */
-	struct misdn_bchannel *holding;
-
-	/*! \brief Next stack in the list of stacks */
-	struct misdn_stack *next;
-};
-
-
-struct misdn_stack* get_stack_by_bc(struct misdn_bchannel *bc);
-
-int isdn_msg_get_index(struct isdn_msg msgs[], msg_t *frm, int nt);
-enum event_e isdn_msg_get_event(struct isdn_msg msgs[], msg_t *frm, int nt);
-int isdn_msg_parse_event(struct isdn_msg msgs[], msg_t *frm, struct misdn_bchannel *bc, int nt);
-char * isdn_get_info(struct isdn_msg msgs[], enum event_e event, int nt);
-msg_t * isdn_msg_build_event(struct isdn_msg msgs[], struct misdn_bchannel *bc, enum event_e event, int nt);
-int isdn_msg_get_index_by_event(struct isdn_msg msgs[], enum event_e event, int nt);
-char * isdn_msg_get_info(struct isdn_msg msgs[], msg_t *msg, int nt);
-
-
-#endif
diff --git a/channels/misdn/isdn_msg_parser.c b/channels/misdn/isdn_msg_parser.c
deleted file mode 100644
index 7b496b7ef5452d7577f8a430d3690a8f51d1c3d7..0000000000000000000000000000000000000000
--- a/channels/misdn/isdn_msg_parser.c
+++ /dev/null
@@ -1,1759 +0,0 @@
-/*
- * Chan_Misdn -- Channel Driver for Asterisk
- *
- * Interface to mISDN
- *
- * Copyright (C) 2004, Christian Richter
- *
- * Christian Richter <crich@beronet.com>
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License
- */
-
-/*! \file
- * \brief Interface to mISDN - message parser
- * \author Christian Richter <crich@beronet.com>
- */
-
-/*** MODULEINFO
-	<support_level>extended</support_level>
- ***/
-
-#include "isdn_lib_intern.h"
-
-
-#include "isdn_lib.h"
-
-#include "ie.c"
-
-/*!
- * \internal
- * \brief Build the name, number, name/number display message string
- *
- * \param display Display buffer to fill in
- * \param display_length Length of the display buffer to fill in
- * \param display_format Display format enumeration
- * \param name Name string to use
- * \param number Number string to use
- */
-static void build_display_str(char *display, size_t display_length, int display_format, const char *name, const char *number)
-{
-	display[0] = 0;
-	switch (display_format) {
-	default:
-	case 0:		/* none */
-		break;
-
-	case 1:		/* name */
-		snprintf(display, display_length, "%s", name);
-		break;
-
-	case 2:		/* number */
-		snprintf(display, display_length, "%s", number);
-		break;
-
-	case 3:		/* both */
-		if (name[0] || number[0]) {
-			snprintf(display, display_length, "\"%s\" <%s>", name, number);
-		}
-		break;
-	}
-}
-
-/*!
- * \internal
- * \brief Encode the Facility IE and put it into the message structure.
- *
- * \param ntmode Where the encoded facility was put when in NT mode.
- * \param msg General message structure
- * \param fac Data to encode into the facility ie.
- * \param nt TRUE if in NT mode.
- */
-static void enc_ie_facility(unsigned char **ntmode, msg_t *msg, struct FacParm *fac, int nt)
-{
-	int len;
-	Q931_info_t *qi;
-	unsigned char *p;
-	unsigned char buf[256];
-
-	len = encodeFac(buf, fac);
-	if (len <= 0) {
-		/*
-		 * mISDN does not know how to build the requested facility structure
-		 * Clear facility information
-		 */
-		fac->Function = Fac_None;
-		return;
-	}
-
-	p = msg_put(msg, len);
-	if (nt) {
-		*ntmode = p + 1;
-	} else {
-		qi = (Q931_info_t *) (msg->data + mISDN_HEADER_LEN);
-		qi->QI_ELEMENT(facility) = p - (unsigned char *) qi - sizeof(Q931_info_t);
-	}
-
-	memcpy(p, buf, len);
-
-	/* Clear facility information */
-	fac->Function = Fac_None;
-}
-
-/*!
- * \internal
- * \brief Decode the Facility IE.
- *
- * \param p Encoded facility ie data to decode. (NT mode)
- * \param qi Encoded facility ie data to decode. (TE mode)
- * \param fac Where to put the decoded facility ie data if it is available.
- * \param nt TRUE if in NT mode.
- * \param bc Associated B channel
- */
-static void dec_ie_facility(unsigned char *p, Q931_info_t *qi, struct FacParm *fac, int nt, struct misdn_bchannel *bc)
-{
-	fac->Function = Fac_None;
-
-	if (!nt) {
-		p = NULL;
-		if (qi->QI_ELEMENT(facility)) {
-			p = (unsigned char *) qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(facility) + 1;
-		}
-	}
-	if (!p) {
-		return;
-	}
-
-	if (decodeFac(p, fac)) {
-		cb_log(3, bc->port, "Decoding facility ie failed! Unrecognized facility message?\n");
-	}
-}
-
-
-
-static void set_channel(struct misdn_bchannel *bc, int channel)
-{
-
-	cb_log(3,bc->port,"set_channel: bc->channel:%d channel:%d\n", bc->channel, channel);
-
-
-	if (channel==0xff) {
-		/* any channel */
-		channel=-1;
-	}
-
-	/*  ALERT: is that everytime true ?  */
-	if (channel > 0 && bc->nt ) {
-
-		if (bc->channel && ( bc->channel != 0xff) ) {
-			cb_log(0,bc->port,"We already have a channel (%d)\n", bc->channel);
-		} else {
-			bc->channel = channel;
-			cb_event(EVENT_NEW_CHANNEL,bc,NULL);
-		}
-	}
-
-	if (channel > 0 && !bc->nt ) {
-		bc->channel = channel;
-		cb_event(EVENT_NEW_CHANNEL,bc,NULL);
-	}
-}
-
-static void parse_proceeding (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	CALL_PROCEEDING_t *proceeding = (CALL_PROCEEDING_t *) (msg->data + HEADER_LEN);
-	//struct misdn_stack *stack=get_stack_by_bc(bc);
-
-	{
-		int  exclusive, channel;
-		dec_ie_channel_id(proceeding->CHANNEL_ID, (Q931_info_t *)proceeding, &exclusive, &channel, nt,bc);
-
-		set_channel(bc,channel);
-
-	}
-
-	dec_ie_progress(proceeding->PROGRESS, (Q931_info_t *)proceeding, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
-
-	dec_ie_facility(proceeding->FACILITY, (Q931_info_t *) proceeding, &bc->fac_in, nt, bc);
-
-	/* dec_ie_redir_dn */
-
-#ifdef DEBUG
-	printf("Parsing PROCEEDING Msg\n");
-#endif
-}
-static msg_t *build_proceeding (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	CALL_PROCEEDING_t *proceeding;
-	msg_t *msg =(msg_t*)create_l3msg(CC_PROCEEDING | REQUEST, MT_CALL_PROCEEDING,  bc?bc->l3_id:-1, sizeof(CALL_PROCEEDING_t) ,nt);
-
-	proceeding=(CALL_PROCEEDING_t*)((msg->data+HEADER_LEN));
-
-	enc_ie_channel_id(&proceeding->CHANNEL_ID, msg, 1,bc->channel, nt,bc);
-
-	if (nt)
-		enc_ie_progress(&proceeding->PROGRESS, msg, 0, nt?1:5, 8, nt,bc);
-
-	if (bc->fac_out.Function != Fac_None) {
-		enc_ie_facility(&proceeding->FACILITY, msg, &bc->fac_out, nt);
-	}
-
-	/* enc_ie_redir_dn */
-
-#ifdef DEBUG
-	printf("Building PROCEEDING Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_alerting (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	ALERTING_t *alerting = (ALERTING_t *) (msg->data + HEADER_LEN);
-	//Q931_info_t *qi=(Q931_info_t*)(msg->data+HEADER_LEN);
-
-	dec_ie_facility(alerting->FACILITY, (Q931_info_t *) alerting, &bc->fac_in, nt, bc);
-
-	/* dec_ie_redir_dn */
-
-	dec_ie_progress(alerting->PROGRESS, (Q931_info_t *)alerting, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
-
-#ifdef DEBUG
-	printf("Parsing ALERTING Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_alerting (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	ALERTING_t *alerting;
-	msg_t *msg =(msg_t*)create_l3msg(CC_ALERTING | REQUEST, MT_ALERTING,  bc?bc->l3_id:-1, sizeof(ALERTING_t) ,nt);
-
-	alerting=(ALERTING_t*)((msg->data+HEADER_LEN));
-
-	enc_ie_channel_id(&alerting->CHANNEL_ID, msg, 1,bc->channel, nt,bc);
-
-	if (nt)
-		enc_ie_progress(&alerting->PROGRESS, msg, 0, nt?1:5, 8, nt,bc);
-
-	if (bc->fac_out.Function != Fac_None) {
-		enc_ie_facility(&alerting->FACILITY, msg, &bc->fac_out, nt);
-	}
-
-	/* enc_ie_redir_dn */
-
-#ifdef DEBUG
-	printf("Building ALERTING Msg\n");
-#endif
-	return msg;
-}
-
-
-static void parse_progress (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	PROGRESS_t *progress = (PROGRESS_t *) (msg->data + HEADER_LEN);
-	//Q931_info_t *qi=(Q931_info_t*)(msg->data+HEADER_LEN);
-
-	dec_ie_progress(progress->PROGRESS, (Q931_info_t *)progress, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
-
-	dec_ie_facility(progress->FACILITY, (Q931_info_t *) progress, &bc->fac_in, nt, bc);
-
-#ifdef DEBUG
-	printf("Parsing PROGRESS Msg\n");
-#endif
-}
-
-static msg_t *build_progress (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	PROGRESS_t *progress;
-	msg_t *msg =(msg_t*)create_l3msg(CC_PROGRESS | REQUEST, MT_PROGRESS,  bc?bc->l3_id:-1, sizeof(PROGRESS_t) ,nt);
-
-	progress=(PROGRESS_t*)((msg->data+HEADER_LEN));
-
-	enc_ie_progress(&progress->PROGRESS, msg, 0, nt ? 1 : 5, 8, nt, bc);
-
-	if (bc->fac_out.Function != Fac_None) {
-		enc_ie_facility(&progress->FACILITY, msg, &bc->fac_out, nt);
-	}
-
-#ifdef DEBUG
-	printf("Building PROGRESS Msg\n");
-#endif
-	return msg;
-}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Extract the SETUP message's BC, HLC, and LLC encoded ie contents.
- *
- * \param setup Indexed setup message contents
- * \param nt TRUE if in NT mode.
- * \param bc Associated B channel
- */
-static void extract_setup_Bc_Hlc_Llc(SETUP_t *setup, int nt, struct misdn_bchannel *bc)
-{
-	__u8 *p;
-	Q931_info_t *qi;
-
-	qi = (Q931_info_t *) setup;
-
-	/* Extract Bearer Capability */
-	if (nt) {
-		p = (__u8 *) setup->BEARER;
-	} else {
-		if (qi->QI_ELEMENT(bearer_capability)) {
-			p = (__u8 *) qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(bearer_capability) + 1;
-		} else {
-			p = NULL;
-		}
-	}
-	if (!p || *p == 0 || sizeof(bc->setup_bc_hlc_llc.Bc.Contents) < *p) {
-		bc->setup_bc_hlc_llc.Bc.Length = 0;
-	} else {
-		bc->setup_bc_hlc_llc.Bc.Length = *p;
-		memcpy(bc->setup_bc_hlc_llc.Bc.Contents, p + 1, *p);
-	}
-
-	/* Extract Low Layer Compatibility */
-	if (nt) {
-		p = (__u8 *) setup->LLC;
-	} else {
-		if (qi->QI_ELEMENT(llc)) {
-			p = (__u8 *) qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(llc) + 1;
-		} else {
-			p = NULL;
-		}
-	}
-	if (!p || *p == 0 || sizeof(bc->setup_bc_hlc_llc.Llc.Contents) < *p) {
-		bc->setup_bc_hlc_llc.Llc.Length = 0;
-	} else {
-		bc->setup_bc_hlc_llc.Llc.Length = *p;
-		memcpy(bc->setup_bc_hlc_llc.Llc.Contents, p + 1, *p);
-	}
-
-	/* Extract High Layer Compatibility */
-	if (nt) {
-		p = (__u8 *) setup->HLC;
-	} else {
-		if (qi->QI_ELEMENT(hlc)) {
-			p = (__u8 *) qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(hlc) + 1;
-		} else {
-			p = NULL;
-		}
-	}
-	if (!p || *p == 0 || sizeof(bc->setup_bc_hlc_llc.Hlc.Contents) < *p) {
-		bc->setup_bc_hlc_llc.Hlc.Length = 0;
-	} else {
-		bc->setup_bc_hlc_llc.Hlc.Length = *p;
-		memcpy(bc->setup_bc_hlc_llc.Hlc.Contents, p + 1, *p);
-	}
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-static void parse_setup (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	SETUP_t *setup = (SETUP_t *) (msg->data + HEADER_LEN);
-	Q931_info_t *qi = (Q931_info_t *) (msg->data + HEADER_LEN);
-	int type;
-	int plan;
-	int present;
-	int screen;
-	int reason;
-
-#ifdef DEBUG
-	printf("Parsing SETUP Msg\n");
-#endif
-
-	dec_ie_calling_pn(setup->CALLING_PN, qi, &type, &plan, &present, &screen, bc->caller.number, sizeof(bc->caller.number), nt, bc);
-	bc->caller.number_type = type;
-	bc->caller.number_plan = plan;
-	switch (present) {
-	default:
-	case 0:
-		bc->caller.presentation = 0;	/* presentation allowed */
-		break;
-	case 1:
-		bc->caller.presentation = 1;	/* presentation restricted */
-		break;
-	case 2:
-		bc->caller.presentation = 2;	/* Number not available */
-		break;
-	}
-	if (0 <= screen) {
-		bc->caller.screening = screen;
-	} else {
-		bc->caller.screening = 0;	/* Unscreened */
-	}
-
-	dec_ie_facility(setup->FACILITY, (Q931_info_t *) setup, &bc->fac_in, nt, bc);
-
-	dec_ie_called_pn(setup->CALLED_PN, (Q931_info_t *) setup, &type, &plan, bc->dialed.number, sizeof(bc->dialed.number), nt, bc);
-	bc->dialed.number_type = type;
-	bc->dialed.number_plan = plan;
-
-	dec_ie_keypad(setup->KEYPAD, (Q931_info_t *) setup, bc->keypad, sizeof(bc->keypad), nt, bc);
-
-	dec_ie_complete(setup->COMPLETE, (Q931_info_t *) setup, &bc->sending_complete, nt, bc);
-
-	dec_ie_redir_nr(setup->REDIR_NR, (Q931_info_t *) setup, &type, &plan, &present, &screen, &reason, bc->redirecting.from.number, sizeof(bc->redirecting.from.number), nt, bc);
-	bc->redirecting.from.number_type = type;
-	bc->redirecting.from.number_plan = plan;
-	switch (present) {
-	default:
-	case 0:
-		bc->redirecting.from.presentation = 0;	/* presentation allowed */
-		break;
-	case 1:
-		bc->redirecting.from.presentation = 1;	/* presentation restricted */
-		break;
-	case 2:
-		bc->redirecting.from.presentation = 2;	/* Number not available */
-		break;
-	}
-	if (0 <= screen) {
-		bc->redirecting.from.screening = screen;
-	} else {
-		bc->redirecting.from.screening = 0;	/* Unscreened */
-	}
-	if (0 <= reason) {
-		bc->redirecting.reason = reason;
-	} else {
-		bc->redirecting.reason = mISDN_REDIRECTING_REASON_UNKNOWN;
-	}
-
-	{
-		int  coding, capability, mode, rate, multi, user, async, urate, stopbits, dbits, parity;
-
-		dec_ie_bearer(setup->BEARER, (Q931_info_t *)setup, &coding, &capability, &mode, &rate, &multi, &user, &async, &urate, &stopbits, &dbits, &parity, nt,bc);
-		switch (capability) {
-		case -1: bc->capability=INFO_CAPABILITY_DIGITAL_UNRESTRICTED;
-			break;
-		case 0: bc->capability=INFO_CAPABILITY_SPEECH;
-			break;
-		case 18: bc->capability=INFO_CAPABILITY_VIDEO;
-			break;
-		case 8: bc->capability=INFO_CAPABILITY_DIGITAL_UNRESTRICTED;
-			bc->user1 = user;
-			bc->urate = urate;
-
-			bc->rate = rate;
-			bc->mode = mode;
-			break;
-		case 9: bc->capability=INFO_CAPABILITY_DIGITAL_RESTRICTED;
-			break;
-		default:
-			break;
-		}
-
-		switch(user) {
-		case 2:
-			bc->law=INFO_CODEC_ULAW;
-			break;
-		case 3:
-			bc->law=INFO_CODEC_ALAW;
-			break;
-		default:
-			bc->law=INFO_CODEC_ALAW;
-
-		}
-
-		bc->capability=capability;
-	}
-	{
-		int  exclusive, channel;
-		dec_ie_channel_id(setup->CHANNEL_ID, (Q931_info_t *)setup, &exclusive, &channel, nt,bc);
-
-		set_channel(bc,channel);
-	}
-
-	{
-		int  protocol ;
-		dec_ie_useruser(setup->USER_USER, (Q931_info_t *)setup, &protocol, bc->uu, &bc->uulen, nt,bc);
-		if (bc->uulen) cb_log(1, bc->port, "USERUSERINFO:%s\n", bc->uu);
-		else
-		cb_log(1, bc->port, "NO USERUSERINFO\n");
-	}
-
-	dec_ie_progress(setup->PROGRESS, (Q931_info_t *)setup, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	extract_setup_Bc_Hlc_Llc(setup, nt, bc);
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-}
-
-#define ANY_CHANNEL 0xff /* IE attribute for 'any channel' */
-static msg_t *build_setup (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	SETUP_t *setup;
-	msg_t *msg =(msg_t*)create_l3msg(CC_SETUP | REQUEST, MT_SETUP,  bc?bc->l3_id:-1, sizeof(SETUP_t) ,nt);
-	int is_ptp;
-	enum FacFunction fac_type;
-
-	setup=(SETUP_t*)((msg->data+HEADER_LEN));
-
-	if (bc->channel == 0 || bc->channel == ANY_CHANNEL || bc->channel==-1)
-		enc_ie_channel_id(&setup->CHANNEL_ID, msg, 0, bc->channel, nt,bc);
-	else
-		enc_ie_channel_id(&setup->CHANNEL_ID, msg, 1, bc->channel, nt,bc);
-
-	fac_type = bc->fac_out.Function;
-	if (fac_type != Fac_None) {
-		enc_ie_facility(&setup->FACILITY, msg, &bc->fac_out, nt);
-	}
-
-	enc_ie_calling_pn(&setup->CALLING_PN, msg, bc->caller.number_type, bc->caller.number_plan,
-		bc->caller.presentation, bc->caller.screening, bc->caller.number, nt, bc);
-
-	if (bc->dialed.number[0]) {
-		enc_ie_called_pn(&setup->CALLED_PN, msg, bc->dialed.number_type, bc->dialed.number_plan, bc->dialed.number, nt, bc);
-	}
-
-	switch (bc->outgoing_colp) {
-	case 0:/* pass */
-	case 1:/* restricted */
-		is_ptp = misdn_lib_is_ptp(bc->port);
-		if (bc->redirecting.from.number[0]
-			&& ((!is_ptp && nt)
-				|| (is_ptp
-#if defined(AST_MISDN_ENHANCEMENTS)
-					/*
-					 * There is no need to send out this ie when we are also sending
-					 * a Fac_DivertingLegInformation2 as well.  The
-					 * Fac_DivertingLegInformation2 supercedes the information in
-					 * this ie.
-					 */
-					&& fac_type != Fac_DivertingLegInformation2
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-			))) {
-#if 1
-			/* ETSI and Q.952 do not define the screening field */
-			enc_ie_redir_nr(&setup->REDIR_NR, msg, bc->redirecting.from.number_type,
-				bc->redirecting.from.number_plan, bc->redirecting.from.presentation, 0,
-				bc->redirecting.reason, bc->redirecting.from.number, nt, bc);
-#else
-			/* Q.931 defines the screening field */
-			enc_ie_redir_nr(&setup->REDIR_NR, msg, bc->redirecting.from.number_type,
-				bc->redirecting.from.number_plan, bc->redirecting.from.presentation,
-				bc->redirecting.from.screening, bc->redirecting.reason,
-				bc->redirecting.from.number, nt, bc);
-#endif
-		}
-		break;
-	default:
-		break;
-	}
-
-	if (bc->keypad[0]) {
-		enc_ie_keypad(&setup->KEYPAD, msg, bc->keypad, nt,bc);
-	}
-
-
-
-	if (*bc->display) {
-		enc_ie_display(&setup->DISPLAY, msg, bc->display, nt, bc);
-	} else if (nt && bc->caller.presentation == 0) {
-		char display[sizeof(bc->display)];
-
-		/* Presentation is allowed */
-		build_display_str(display, sizeof(display), bc->display_setup, bc->caller.name, bc->caller.number);
-		if (display[0]) {
-			enc_ie_display(&setup->DISPLAY, msg, display, nt, bc);
-		}
-	}
-
-	{
-		int coding = 0;
-		int capability;
-		int mode = 0;	/* 2 for packet! */
-		int user;
-		int rate = 0x10;
-
-		switch (bc->law) {
-		case INFO_CODEC_ULAW: user=2;
-			break;
-		case INFO_CODEC_ALAW: user=3;
-			break;
-		default:
-			user=3;
-		}
-
-		switch (bc->capability) {
-		case INFO_CAPABILITY_SPEECH: capability = 0;
-			break;
-		case INFO_CAPABILITY_DIGITAL_UNRESTRICTED: capability = 8;
-			user=-1;
-			mode=bc->mode;
-			rate=bc->rate;
-			break;
-		case INFO_CAPABILITY_DIGITAL_RESTRICTED: capability = 9;
-			user=-1;
-			break;
-		default:
-			capability=bc->capability;
-		}
-
-		enc_ie_bearer(&setup->BEARER, msg, coding, capability, mode, rate, -1, user, nt,bc);
-	}
-
-	if (bc->sending_complete) {
-		enc_ie_complete(&setup->COMPLETE,msg, bc->sending_complete, nt, bc);
-	}
-
-	if (bc->uulen) {
-		int  protocol=4;
-		enc_ie_useruser(&setup->USER_USER, msg, protocol, bc->uu, bc->uulen, nt,bc);
-		cb_log(1, bc->port, "ENCODING USERUSERINFO:%s\n", bc->uu);
-	}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	extract_setup_Bc_Hlc_Llc(setup, nt, bc);
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#ifdef DEBUG
-	printf("Building SETUP Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_connect (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	CONNECT_t *connect = (CONNECT_t *) (msg->data + HEADER_LEN);
-	int type;
-	int plan;
-	int pres;
-	int screen;
-
-	bc->ces = connect->ces;
-
-	dec_ie_progress(connect->PROGRESS, (Q931_info_t *)connect, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
-
-	dec_ie_connected_pn(connect->CONNECT_PN, (Q931_info_t *) connect, &type, &plan,
-		&pres, &screen, bc->connected.number, sizeof(bc->connected.number), nt, bc);
-	bc->connected.number_type = type;
-	bc->connected.number_plan = plan;
-	switch (pres) {
-	default:
-	case 0:
-		bc->connected.presentation = 0;	/* presentation allowed */
-		break;
-	case 1:
-		bc->connected.presentation = 1;	/* presentation restricted */
-		break;
-	case 2:
-		bc->connected.presentation = 2;	/* Number not available */
-		break;
-	}
-	if (0 <= screen) {
-		bc->connected.screening = screen;
-	} else {
-		bc->connected.screening = 0;	/* Unscreened */
-	}
-
-	dec_ie_facility(connect->FACILITY, (Q931_info_t *) connect, &bc->fac_in, nt, bc);
-
-	/*
-		cb_log(1,bc->port,"CONNETED PN: %s cpn_dialplan:%d\n", connected_pn, type);
-	*/
-
-#ifdef DEBUG
-	printf("Parsing CONNECT Msg\n");
-#endif
-}
-
-static msg_t *build_connect (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	CONNECT_t *connect;
-	msg_t *msg =(msg_t*)create_l3msg(CC_CONNECT | REQUEST, MT_CONNECT,  bc?bc->l3_id:-1, sizeof(CONNECT_t) ,nt);
-
-	cb_log(6,bc->port,"BUILD_CONNECT: bc:%p bc->l3id:%d, nt:%d\n",bc,bc->l3_id,nt);
-
-	connect=(CONNECT_t*)((msg->data+HEADER_LEN));
-
-	if (nt) {
-		time_t now;
-		time(&now);
-		enc_ie_date(&connect->DATE, msg, now, nt,bc);
-	}
-
-	switch (bc->outgoing_colp) {
-	case 0:/* pass */
-	case 1:/* restricted */
-		enc_ie_connected_pn(&connect->CONNECT_PN, msg, bc->connected.number_type,
-			bc->connected.number_plan, bc->connected.presentation,
-			bc->connected.screening, bc->connected.number, nt, bc);
-		break;
-	default:
-		break;
-	}
-
-	if (nt && bc->connected.presentation == 0) {
-		char display[sizeof(bc->display)];
-
-		/* Presentation is allowed */
-		build_display_str(display, sizeof(display), bc->display_connected, bc->connected.name, bc->connected.number);
-		if (display[0]) {
-			enc_ie_display(&connect->DISPLAY, msg, display, nt, bc);
-		}
-	}
-
-	if (bc->fac_out.Function != Fac_None) {
-		enc_ie_facility(&connect->FACILITY, msg, &bc->fac_out, nt);
-	}
-
-#ifdef DEBUG
-	printf("Building CONNECT Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_setup_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	SETUP_ACKNOWLEDGE_t *setup_acknowledge = (SETUP_ACKNOWLEDGE_t *) (msg->data + HEADER_LEN);
-
-	{
-		int  exclusive, channel;
-		dec_ie_channel_id(setup_acknowledge->CHANNEL_ID, (Q931_info_t *)setup_acknowledge, &exclusive, &channel, nt,bc);
-
-
-		set_channel(bc, channel);
-	}
-
-	dec_ie_progress(setup_acknowledge->PROGRESS, (Q931_info_t *)setup_acknowledge, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
-
-	dec_ie_facility(setup_acknowledge->FACILITY, (Q931_info_t *) setup_acknowledge, &bc->fac_in, nt, bc);
-
-#ifdef DEBUG
-	printf("Parsing SETUP_ACKNOWLEDGE Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_setup_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	SETUP_ACKNOWLEDGE_t *setup_acknowledge;
-	msg_t *msg =(msg_t*)create_l3msg(CC_SETUP_ACKNOWLEDGE | REQUEST, MT_SETUP_ACKNOWLEDGE,  bc?bc->l3_id:-1, sizeof(SETUP_ACKNOWLEDGE_t) ,nt);
-
-	setup_acknowledge=(SETUP_ACKNOWLEDGE_t*)((msg->data+HEADER_LEN));
-
-	enc_ie_channel_id(&setup_acknowledge->CHANNEL_ID, msg, 1,bc->channel, nt,bc);
-
-	if (nt)
-		enc_ie_progress(&setup_acknowledge->PROGRESS, msg, 0, nt?1:5, 8, nt,bc);
-
-	if (bc->fac_out.Function != Fac_None) {
-		enc_ie_facility(&setup_acknowledge->FACILITY, msg, &bc->fac_out, nt);
-	}
-
-#ifdef DEBUG
-	printf("Building SETUP_ACKNOWLEDGE Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_connect_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing CONNECT_ACKNOWLEDGE Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_connect_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	CONNECT_ACKNOWLEDGE_t *connect_acknowledge;
-	msg_t *msg =(msg_t*)create_l3msg(CC_CONNECT | RESPONSE, MT_CONNECT,  bc?bc->l3_id:-1, sizeof(CONNECT_ACKNOWLEDGE_t) ,nt);
-
-	connect_acknowledge=(CONNECT_ACKNOWLEDGE_t*)((msg->data+HEADER_LEN));
-
-	enc_ie_channel_id(&connect_acknowledge->CHANNEL_ID, msg, 1, bc->channel, nt,bc);
-
-#ifdef DEBUG
-	printf("Building CONNECT_ACKNOWLEDGE Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_user_information (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing USER_INFORMATION Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_user_information (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_USER_INFORMATION | REQUEST, MT_USER_INFORMATION,  bc?bc->l3_id:-1, sizeof(USER_INFORMATION_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building USER_INFORMATION Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_suspend_reject (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing SUSPEND_REJECT Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_suspend_reject (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_SUSPEND_REJECT | REQUEST, MT_SUSPEND_REJECT,  bc?bc->l3_id:-1, sizeof(SUSPEND_REJECT_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building SUSPEND_REJECT Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_resume_reject (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing RESUME_REJECT Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_resume_reject (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_RESUME_REJECT | REQUEST, MT_RESUME_REJECT,  bc?bc->l3_id:-1, sizeof(RESUME_REJECT_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building RESUME_REJECT Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_hold (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing HOLD Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_hold (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_HOLD | REQUEST, MT_HOLD,  bc?bc->l3_id:-1, sizeof(HOLD_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building HOLD Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_suspend (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing SUSPEND Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_suspend (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_SUSPEND | REQUEST, MT_SUSPEND,  bc?bc->l3_id:-1, sizeof(SUSPEND_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building SUSPEND Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_resume (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing RESUME Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_resume (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_RESUME | REQUEST, MT_RESUME,  bc?bc->l3_id:-1, sizeof(RESUME_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building RESUME Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_hold_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing HOLD_ACKNOWLEDGE Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_hold_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_HOLD_ACKNOWLEDGE | REQUEST, MT_HOLD_ACKNOWLEDGE,  bc?bc->l3_id:-1, sizeof(HOLD_ACKNOWLEDGE_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building HOLD_ACKNOWLEDGE Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_suspend_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing SUSPEND_ACKNOWLEDGE Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_suspend_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_SUSPEND_ACKNOWLEDGE | REQUEST, MT_SUSPEND_ACKNOWLEDGE,  bc?bc->l3_id:-1, sizeof(SUSPEND_ACKNOWLEDGE_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building SUSPEND_ACKNOWLEDGE Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_resume_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing RESUME_ACKNOWLEDGE Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_resume_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_RESUME_ACKNOWLEDGE | REQUEST, MT_RESUME_ACKNOWLEDGE,  bc?bc->l3_id:-1, sizeof(RESUME_ACKNOWLEDGE_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building RESUME_ACKNOWLEDGE Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_hold_reject (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing HOLD_REJECT Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_hold_reject (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_HOLD_REJECT | REQUEST, MT_HOLD_REJECT,  bc?bc->l3_id:-1, sizeof(HOLD_REJECT_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building HOLD_REJECT Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_retrieve (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing RETRIEVE Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_retrieve (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_RETRIEVE | REQUEST, MT_RETRIEVE,  bc?bc->l3_id:-1, sizeof(RETRIEVE_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building RETRIEVE Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_retrieve_acknowledge (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing RETRIEVE_ACKNOWLEDGE Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_retrieve_acknowledge (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	RETRIEVE_ACKNOWLEDGE_t *retrieve_acknowledge;
-	msg_t *msg =(msg_t*)create_l3msg(CC_RETRIEVE_ACKNOWLEDGE | REQUEST, MT_RETRIEVE_ACKNOWLEDGE,  bc?bc->l3_id:-1, sizeof(RETRIEVE_ACKNOWLEDGE_t) ,nt);
-
-	retrieve_acknowledge=(RETRIEVE_ACKNOWLEDGE_t*)((msg->data+HEADER_LEN));
-
-	enc_ie_channel_id(&retrieve_acknowledge->CHANNEL_ID, msg, 1, bc->channel, nt,bc);
-#ifdef DEBUG
-	printf("Building RETRIEVE_ACKNOWLEDGE Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_retrieve_reject (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing RETRIEVE_REJECT Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_retrieve_reject (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_RETRIEVE_REJECT | REQUEST, MT_RETRIEVE_REJECT,  bc?bc->l3_id:-1, sizeof(RETRIEVE_REJECT_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building RETRIEVE_REJECT Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_disconnect (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	DISCONNECT_t *disconnect = (DISCONNECT_t *) (msg->data + HEADER_LEN);
-	int location;
- 	int cause;
-	dec_ie_cause(disconnect->CAUSE, (Q931_info_t *)(disconnect), &location, &cause, nt,bc);
-	if (cause>0) bc->cause=cause;
-
-	dec_ie_facility(disconnect->FACILITY, (Q931_info_t *) disconnect, &bc->fac_in, nt, bc);
-
-	dec_ie_progress(disconnect->PROGRESS, (Q931_info_t *)disconnect, &bc->progress_coding, &bc->progress_location, &bc->progress_indicator, nt, bc);
-#ifdef DEBUG
-	printf("Parsing DISCONNECT Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_disconnect (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	DISCONNECT_t *disconnect;
-	msg_t *msg =(msg_t*)create_l3msg(CC_DISCONNECT | REQUEST, MT_DISCONNECT,  bc?bc->l3_id:-1, sizeof(DISCONNECT_t) ,nt);
-
-	disconnect=(DISCONNECT_t*)((msg->data+HEADER_LEN));
-
-	enc_ie_cause(&disconnect->CAUSE, msg, (nt)?1:0, bc->out_cause,nt,bc);
-	if (nt) {
-		enc_ie_progress(&disconnect->PROGRESS, msg, 0, nt ? 1 : 5, 8, nt, bc);
-	}
-
-	if (bc->fac_out.Function != Fac_None) {
-		enc_ie_facility(&disconnect->FACILITY, msg, &bc->fac_out, nt);
-	}
-
-	if (bc->uulen) {
-		int  protocol=4;
-		enc_ie_useruser(&disconnect->USER_USER, msg, protocol, bc->uu, bc->uulen, nt,bc);
-		cb_log(1, bc->port, "ENCODING USERUSERINFO:%s\n", bc->uu);
-	}
-
-#ifdef DEBUG
-	printf("Building DISCONNECT Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_restart (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	RESTART_t *restart = (RESTART_t *) (msg->data + HEADER_LEN);
-
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-
-#ifdef DEBUG
-	printf("Parsing RESTART Msg\n");
-#endif
-
-	{
-		int  exclusive;
-		dec_ie_channel_id(restart->CHANNEL_ID, (Q931_info_t *)restart, &exclusive, &bc->restart_channel, nt,bc);
-		cb_log(3, stack->port, "CC_RESTART Request on channel:%d on this port.\n", bc->restart_channel);
-	}
-
-}
-
-static msg_t *build_restart (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	RESTART_t *restart;
-	msg_t *msg =(msg_t*)create_l3msg(CC_RESTART | REQUEST, MT_RESTART,  bc?bc->l3_id:-1, sizeof(RESTART_t) ,nt);
-
-	restart=(RESTART_t*)((msg->data+HEADER_LEN));
-
-#ifdef DEBUG
-	printf("Building RESTART Msg\n");
-#endif
-
-	if (bc->channel > 0) {
-		enc_ie_channel_id(&restart->CHANNEL_ID, msg, 1,bc->channel, nt,bc);
-		enc_ie_restart_ind(&restart->RESTART_IND, msg, 0x80, nt, bc);
-	} else {
-		enc_ie_restart_ind(&restart->RESTART_IND, msg, 0x87, nt, bc);
-	}
-
-	cb_log(0,bc->port, "Restarting channel %d\n", bc->channel);
-	return msg;
-}
-
-static void parse_release (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	RELEASE_t *release = (RELEASE_t *) (msg->data + HEADER_LEN);
-	int location;
-	int cause;
-
-	dec_ie_cause(release->CAUSE, (Q931_info_t *)(release), &location, &cause, nt,bc);
-	if (cause>0) bc->cause=cause;
-
-	dec_ie_facility(release->FACILITY, (Q931_info_t *) release, &bc->fac_in, nt, bc);
-
-#ifdef DEBUG
-	printf("Parsing RELEASE Msg\n");
-#endif
-
-
-}
-
-static msg_t *build_release (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	RELEASE_t *release;
-	msg_t *msg =(msg_t*)create_l3msg(CC_RELEASE | REQUEST, MT_RELEASE,  bc?bc->l3_id:-1, sizeof(RELEASE_t) ,nt);
-
-	release=(RELEASE_t*)((msg->data+HEADER_LEN));
-
-	if (bc->out_cause>= 0)
-		enc_ie_cause(&release->CAUSE, msg, nt?1:0, bc->out_cause, nt,bc);
-
-	if (bc->fac_out.Function != Fac_None) {
-		enc_ie_facility(&release->FACILITY, msg, &bc->fac_out, nt);
-	}
-
-	if (bc->uulen) {
-		int  protocol=4;
-		enc_ie_useruser(&release->USER_USER, msg, protocol, bc->uu, bc->uulen, nt,bc);
-		cb_log(1, bc->port, "ENCODING USERUSERINFO:%s\n", bc->uu);
-	}
-
-#ifdef DEBUG
-	printf("Building RELEASE Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_release_complete (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	RELEASE_COMPLETE_t *release_complete = (RELEASE_COMPLETE_t *) (msg->data + HEADER_LEN);
-	int location;
-	int cause;
-	iframe_t *frm = (iframe_t*) msg->data;
-
-	struct misdn_stack *stack=get_stack_by_bc(bc);
-	mISDNuser_head_t *hh;
-	hh=(mISDNuser_head_t*)msg->data;
-
-	/*hh=(mISDN_head_t*)msg->data;
-	mISDN_head_t *hh;*/
-
-	if (nt) {
-		if (hh->prim == (CC_RELEASE_COMPLETE|CONFIRM)) {
-			cb_log(0, stack->port, "CC_RELEASE_COMPLETE|CONFIRM [NT] \n");
-			return;
-		}
-	} else {
-		if (frm->prim == (CC_RELEASE_COMPLETE|CONFIRM)) {
-			cb_log(0, stack->port, "CC_RELEASE_COMPLETE|CONFIRM [TE] \n");
-			return;
-		}
-	}
-	dec_ie_cause(release_complete->CAUSE, (Q931_info_t *)(release_complete), &location, &cause, nt,bc);
-	if (cause>0) bc->cause=cause;
-
-	dec_ie_facility(release_complete->FACILITY, (Q931_info_t *) release_complete, &bc->fac_in, nt, bc);
-
-#ifdef DEBUG
-	printf("Parsing RELEASE_COMPLETE Msg\n");
-#endif
-}
-
-static msg_t *build_release_complete (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	RELEASE_COMPLETE_t *release_complete;
-	msg_t *msg =(msg_t*)create_l3msg(CC_RELEASE_COMPLETE | REQUEST, MT_RELEASE_COMPLETE,  bc?bc->l3_id:-1, sizeof(RELEASE_COMPLETE_t) ,nt);
-
-	release_complete=(RELEASE_COMPLETE_t*)((msg->data+HEADER_LEN));
-
-	enc_ie_cause(&release_complete->CAUSE, msg, nt?1:0, bc->out_cause, nt,bc);
-
-	if (bc->fac_out.Function != Fac_None) {
-		enc_ie_facility(&release_complete->FACILITY, msg, &bc->fac_out, nt);
-	}
-
-	if (bc->uulen) {
-		int  protocol=4;
-		enc_ie_useruser(&release_complete->USER_USER, msg, protocol, bc->uu, bc->uulen, nt,bc);
-		cb_log(1, bc->port, "ENCODING USERUSERINFO:%s\n", bc->uu);
-	}
-
-#ifdef DEBUG
-	printf("Building RELEASE_COMPLETE Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_facility (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt ? mISDNUSER_HEAD_SIZE : mISDN_HEADER_LEN;
-	FACILITY_t *facility = (FACILITY_t*)(msg->data+HEADER_LEN);
-	Q931_info_t *qi = (Q931_info_t*)(msg->data+HEADER_LEN);
-	unsigned char *p = NULL;
-#if defined(AST_MISDN_ENHANCEMENTS)
-	int description_code;
-	int type;
-	int plan;
-	int present;
-	char number[sizeof(bc->redirecting.to.number)];
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#ifdef DEBUG
-	printf("Parsing FACILITY Msg\n");
-#endif
-
-	bc->fac_in.Function = Fac_None;
-
-	if (!bc->nt) {
-		if (qi->QI_ELEMENT(facility))
-			p = (unsigned char *)qi + sizeof(Q931_info_t) + qi->QI_ELEMENT(facility) + 1;
-	} else {
-		p = facility->FACILITY;
-	}
-	if (!p)
-		return;
-
-	if (decodeFac(p, &bc->fac_in)) {
-		cb_log(3, bc->port, "Decoding facility ie failed! Unrecognized facility message?\n");
-	}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	dec_ie_notify(facility->NOTIFY, qi, &description_code, nt, bc);
-	if (description_code < 0) {
-		bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID;
-	} else {
-		bc->notify_description_code = description_code;
-	}
-
-	dec_ie_redir_dn(facility->REDIR_DN, qi, &type, &plan, &present, number, sizeof(number), nt, bc);
-	if (0 <= type) {
-		bc->redirecting.to_changed = 1;
-
-		bc->redirecting.to.number_type = type;
-		bc->redirecting.to.number_plan = plan;
-		switch (present) {
-		default:
-		case 0:
-			bc->redirecting.to.presentation = 0;	/* presentation allowed */
-			break;
-		case 1:
-			bc->redirecting.to.presentation = 1;	/* presentation restricted */
-			break;
-		case 2:
-			bc->redirecting.to.presentation = 2;	/* Number not available */
-			break;
-		}
-		bc->redirecting.to.screening = 0;	/* Unscreened */
-		strcpy(bc->redirecting.to.number, number);
-	}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-}
-
-static msg_t *build_facility (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int len;
-	int HEADER_LEN;
-	unsigned char *ie_fac;
-	unsigned char fac_tmp[256];
-	msg_t *msg;
-	FACILITY_t *facility;
-	Q931_info_t *qi;
-
-#ifdef DEBUG
-	printf("Building FACILITY Msg\n");
-#endif
-
-	len = encodeFac(fac_tmp, &(bc->fac_out));
-	if (len <= 0) {
-		/*
-		 * mISDN does not know how to build the requested facility structure
-		 * Clear facility information
-		 */
-		bc->fac_out.Function = Fac_None;
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-		/* Clear other one shot information. */
-		bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID;
-		bc->redirecting.to_changed = 0;
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-		return NULL;
-	}
-
-	msg = (msg_t *) create_l3msg(CC_FACILITY | REQUEST, MT_FACILITY, bc ? bc->l3_id : -1, sizeof(FACILITY_t), nt);
-	HEADER_LEN = nt ? mISDNUSER_HEAD_SIZE : mISDN_HEADER_LEN;
-	facility = (FACILITY_t *) (msg->data + HEADER_LEN);
-
-	ie_fac = msg_put(msg, len);
-	if (bc->nt) {
-		facility->FACILITY = ie_fac + 1;
-	} else {
-		qi = (Q931_info_t *)(msg->data + mISDN_HEADER_LEN);
-		qi->QI_ELEMENT(facility) = ie_fac - (unsigned char *)qi - sizeof(Q931_info_t);
-	}
-
-	memcpy(ie_fac, fac_tmp, len);
-
-	/* Clear facility information */
-	bc->fac_out.Function = Fac_None;
-
-	if (*bc->display) {
-#ifdef DEBUG
-		printf("Sending %s as Display\n", bc->display);
-#endif
-		enc_ie_display(&facility->DISPLAY, msg, bc->display, nt,bc);
-	}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-	if (bc->notify_description_code != mISDN_NOTIFY_CODE_INVALID) {
-		enc_ie_notify(&facility->NOTIFY, msg, bc->notify_description_code, nt, bc);
-		bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID;
-	}
-
-	if (bc->redirecting.to_changed) {
-		bc->redirecting.to_changed = 0;
-		switch (bc->outgoing_colp) {
-		case 0:/* pass */
-		case 1:/* restricted */
-			enc_ie_redir_dn(&facility->REDIR_DN, msg, bc->redirecting.to.number_type,
-				bc->redirecting.to.number_plan, bc->redirecting.to.presentation,
-				bc->redirecting.to.number, nt, bc);
-			break;
-		default:
-			break;
-		}
-	}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-	return msg;
-}
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Parse a received REGISTER message
- *
- * \param msgs Search table entry that called us.
- * \param msg Received message contents
- * \param bc Associated B channel
- * \param nt TRUE if in NT mode.
- */
-static void parse_register(struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN;
-	REGISTER_t *reg;
-
-	HEADER_LEN = nt ? mISDNUSER_HEAD_SIZE : mISDN_HEADER_LEN;
-	reg = (REGISTER_t *) (msg->data + HEADER_LEN);
-
-	/*
-	 * A facility ie is optional.
-	 * The peer may just be establishing a connection to send
-	 * messages later.
-	 */
-	dec_ie_facility(reg->FACILITY, (Q931_info_t *) reg, &bc->fac_in, nt, bc);
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-#if defined(AST_MISDN_ENHANCEMENTS)
-/*!
- * \internal
- * \brief Construct a REGISTER message
- *
- * \param msgs Search table entry that called us.
- * \param bc Associated B channel
- * \param nt TRUE if in NT mode.
- *
- * \return Allocated built message
- */
-static msg_t *build_register(struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN;
-	REGISTER_t *reg;
-	msg_t *msg;
-
-	msg = (msg_t *) create_l3msg(CC_REGISTER | REQUEST, MT_REGISTER,  bc ? bc->l3_id : -1, sizeof(REGISTER_t), nt);
-	HEADER_LEN = nt ? mISDNUSER_HEAD_SIZE : mISDN_HEADER_LEN;
-	reg = (REGISTER_t *) (msg->data + HEADER_LEN);
-
-	if (bc->fac_out.Function != Fac_None) {
-		enc_ie_facility(&reg->FACILITY, msg, &bc->fac_out, nt);
-	}
-
-	return msg;
-}
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-
-static void parse_notify (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt ? mISDNUSER_HEAD_SIZE : mISDN_HEADER_LEN;
-	NOTIFY_t *notify = (NOTIFY_t *) (msg->data + HEADER_LEN);
-	int description_code;
-	int type;
-	int plan;
-	int present;
-	char number[sizeof(bc->redirecting.to.number)];
-
-#ifdef DEBUG
-	printf("Parsing NOTIFY Msg\n");
-#endif
-
-	dec_ie_notify(notify->NOTIFY, (Q931_info_t *) notify, &description_code, nt, bc);
-	if (description_code < 0) {
-		bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID;
-	} else {
-		bc->notify_description_code = description_code;
-	}
-
-	dec_ie_redir_dn(notify->REDIR_DN, (Q931_info_t *) notify, &type, &plan, &present, number, sizeof(number), nt, bc);
-	if (0 <= type) {
-		bc->redirecting.to_changed = 1;
-
-		bc->redirecting.to.number_type = type;
-		bc->redirecting.to.number_plan = plan;
-		switch (present) {
-		default:
-		case 0:
-			bc->redirecting.to.presentation = 0;	/* presentation allowed */
-			break;
-		case 1:
-			bc->redirecting.to.presentation = 1;	/* presentation restricted */
-			break;
-		case 2:
-			bc->redirecting.to.presentation = 2;	/* Number not available */
-			break;
-		}
-		bc->redirecting.to.screening = 0;	/* Unscreened */
-		strcpy(bc->redirecting.to.number, number);
-	}
-}
-
-static msg_t *build_notify (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	NOTIFY_t *notify;
-	msg_t *msg =(msg_t*)create_l3msg(CC_NOTIFY | REQUEST, MT_NOTIFY,  bc?bc->l3_id:-1, sizeof(NOTIFY_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building NOTIFY Msg\n");
-#endif
-
-	notify = (NOTIFY_t *) (msg->data + HEADER_LEN);
-
-	enc_ie_notify(&notify->NOTIFY, msg, bc->notify_description_code, nt, bc);
-	bc->notify_description_code = mISDN_NOTIFY_CODE_INVALID;
-
-	if (bc->redirecting.to_changed) {
-		bc->redirecting.to_changed = 0;
-		switch (bc->outgoing_colp) {
-		case 0:/* pass */
-		case 1:/* restricted */
-			enc_ie_redir_dn(&notify->REDIR_DN, msg, bc->redirecting.to.number_type,
-				bc->redirecting.to.number_plan, bc->redirecting.to.presentation,
-				bc->redirecting.to.number, nt, bc);
-			break;
-		default:
-			break;
-		}
-	}
-	return msg;
-}
-
-static void parse_status_enquiry (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing STATUS_ENQUIRY Msg\n");
-#endif
-}
-
-static msg_t *build_status_enquiry (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_STATUS_ENQUIRY | REQUEST, MT_STATUS_ENQUIRY,  bc?bc->l3_id:-1, sizeof(STATUS_ENQUIRY_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building STATUS_ENQUIRY Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_information (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	INFORMATION_t *information = (INFORMATION_t *) (msg->data + HEADER_LEN);
-	int type, plan;
-
-	dec_ie_called_pn(information->CALLED_PN, (Q931_info_t *) information, &type, &plan, bc->info_dad, sizeof(bc->info_dad), nt, bc);
-	dec_ie_keypad(information->KEYPAD, (Q931_info_t *) information, bc->keypad, sizeof(bc->keypad), nt, bc);
-
-#ifdef DEBUG
-	printf("Parsing INFORMATION Msg\n");
-#endif
-}
-
-static msg_t *build_information (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	INFORMATION_t *information;
-	msg_t *msg =(msg_t*)create_l3msg(CC_INFORMATION | REQUEST, MT_INFORMATION,  bc?bc->l3_id:-1, sizeof(INFORMATION_t) ,nt);
-
-	information=(INFORMATION_t*)((msg->data+HEADER_LEN));
-
-	enc_ie_called_pn(&information->CALLED_PN, msg, 0, 1, bc->info_dad, nt,bc);
-
-	{
-		if (*bc->display) {
-#ifdef DEBUG
-			printf("Sending %s as Display\n", bc->display);
-#endif
-			enc_ie_display(&information->DISPLAY, msg, bc->display, nt,bc);
-		}
-	}
-
-#ifdef DEBUG
-	printf("Building INFORMATION Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_status (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int HEADER_LEN = nt?mISDNUSER_HEAD_SIZE:mISDN_HEADER_LEN;
-	STATUS_t *status = (STATUS_t *) (msg->data + HEADER_LEN);
-	int location;
-	int cause;
-
-	dec_ie_cause(status->CAUSE, (Q931_info_t *)(status), &location, &cause, nt,bc);
-	if (cause>0) bc->cause=cause;
-
-#ifdef DEBUG
-	printf("Parsing STATUS Msg\n");
-#endif
-}
-
-static msg_t *build_status (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_STATUS | REQUEST, MT_STATUS,  bc?bc->l3_id:-1, sizeof(STATUS_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building STATUS Msg\n");
-#endif
-	return msg;
-}
-
-static void parse_timeout (struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-#ifdef DEBUG
-	printf("Parsing STATUS Msg\n");
-#endif
-}
-
-static msg_t *build_timeout (struct isdn_msg msgs[], struct misdn_bchannel *bc, int nt)
-{
-	msg_t *msg =(msg_t*)create_l3msg(CC_STATUS | REQUEST, MT_STATUS,  bc?bc->l3_id:-1, sizeof(STATUS_t) ,nt);
-
-#ifdef DEBUG
-	printf("Building STATUS Msg\n");
-#endif
-	return msg;
-}
-
-
-/************************************/
-
-
-
-
-/** Msg Array **/
-
-struct isdn_msg msgs_g[] = {
-/* *INDENT-OFF* */
-	/* misdn_msg,               event,                      msg_parser,                 msg_builder,                info */
-	{ CC_PROCEEDING,            EVENT_PROCEEDING,           parse_proceeding,           build_proceeding,           "PROCEEDING" },
-	{ CC_ALERTING,              EVENT_ALERTING,             parse_alerting,             build_alerting,             "ALERTING" },
-	{ CC_PROGRESS,              EVENT_PROGRESS,             parse_progress,             build_progress,             "PROGRESS" },
-	{ CC_SETUP,                 EVENT_SETUP,                parse_setup,                build_setup,                "SETUP" },
-#if defined(AST_MISDN_ENHANCEMENTS)
-	{ CC_REGISTER,              EVENT_REGISTER,             parse_register,             build_register,             "REGISTER" },
-#endif	/* defined(AST_MISDN_ENHANCEMENTS) */
-	{ CC_CONNECT,               EVENT_CONNECT,              parse_connect,              build_connect,              "CONNECT" },
-	{ CC_SETUP_ACKNOWLEDGE,     EVENT_SETUP_ACKNOWLEDGE,    parse_setup_acknowledge,    build_setup_acknowledge,    "SETUP_ACKNOWLEDGE" },
-	{ CC_CONNECT_ACKNOWLEDGE,   EVENT_CONNECT_ACKNOWLEDGE,  parse_connect_acknowledge,  build_connect_acknowledge,  "CONNECT_ACKNOWLEDGE " },
-	{ CC_USER_INFORMATION,      EVENT_USER_INFORMATION,     parse_user_information,     build_user_information,     "USER_INFORMATION" },
-	{ CC_SUSPEND_REJECT,        EVENT_SUSPEND_REJECT,       parse_suspend_reject,       build_suspend_reject,       "SUSPEND_REJECT" },
-	{ CC_RESUME_REJECT,         EVENT_RESUME_REJECT,        parse_resume_reject,        build_resume_reject,        "RESUME_REJECT" },
-	{ CC_HOLD,                  EVENT_HOLD,                 parse_hold,                 build_hold,                 "HOLD" },
-	{ CC_SUSPEND,               EVENT_SUSPEND,              parse_suspend,              build_suspend,              "SUSPEND" },
-	{ CC_RESUME,                EVENT_RESUME,               parse_resume,               build_resume,               "RESUME" },
-	{ CC_HOLD_ACKNOWLEDGE,      EVENT_HOLD_ACKNOWLEDGE,     parse_hold_acknowledge,     build_hold_acknowledge,     "HOLD_ACKNOWLEDGE" },
-	{ CC_SUSPEND_ACKNOWLEDGE,   EVENT_SUSPEND_ACKNOWLEDGE,  parse_suspend_acknowledge,  build_suspend_acknowledge,  "SUSPEND_ACKNOWLEDGE" },
-	{ CC_RESUME_ACKNOWLEDGE,    EVENT_RESUME_ACKNOWLEDGE,   parse_resume_acknowledge,   build_resume_acknowledge,   "RESUME_ACKNOWLEDGE" },
-	{ CC_HOLD_REJECT,           EVENT_HOLD_REJECT,          parse_hold_reject,          build_hold_reject,          "HOLD_REJECT" },
-	{ CC_RETRIEVE,              EVENT_RETRIEVE,             parse_retrieve,             build_retrieve,             "RETRIEVE" },
-	{ CC_RETRIEVE_ACKNOWLEDGE,  EVENT_RETRIEVE_ACKNOWLEDGE, parse_retrieve_acknowledge, build_retrieve_acknowledge, "RETRIEVE_ACKNOWLEDGE" },
-	{ CC_RETRIEVE_REJECT,       EVENT_RETRIEVE_REJECT,      parse_retrieve_reject,      build_retrieve_reject,      "RETRIEVE_REJECT" },
-	{ CC_DISCONNECT,            EVENT_DISCONNECT,           parse_disconnect,           build_disconnect,           "DISCONNECT" },
-	{ CC_RESTART,               EVENT_RESTART,              parse_restart,              build_restart,              "RESTART" },
-	{ CC_RELEASE,               EVENT_RELEASE,              parse_release,              build_release,              "RELEASE" },
-	{ CC_RELEASE_COMPLETE,      EVENT_RELEASE_COMPLETE,     parse_release_complete,     build_release_complete,     "RELEASE_COMPLETE" },
-	{ CC_FACILITY,              EVENT_FACILITY,             parse_facility,             build_facility,             "FACILITY" },
-	{ CC_NOTIFY,                EVENT_NOTIFY,               parse_notify,               build_notify,               "NOTIFY" },
-	{ CC_STATUS_ENQUIRY,        EVENT_STATUS_ENQUIRY,       parse_status_enquiry,       build_status_enquiry,       "STATUS_ENQUIRY" },
-	{ CC_INFORMATION,           EVENT_INFORMATION,          parse_information,          build_information,          "INFORMATION" },
-	{ CC_STATUS,                EVENT_STATUS,               parse_status,               build_status,               "STATUS" },
-	{ CC_TIMEOUT,               EVENT_TIMEOUT,              parse_timeout,              build_timeout,              "TIMEOUT" },
-	{ 0, 0, NULL, NULL, NULL }
-/* *INDENT-ON* */
-};
-
-#define msgs_max (sizeof(msgs_g)/sizeof(struct isdn_msg))
-
-/** INTERFACE FCTS ***/
-int isdn_msg_get_index(struct isdn_msg msgs[], msg_t *msg, int nt)
-{
-	int i;
-
-	if (nt){
-		mISDNuser_head_t *hh = (mISDNuser_head_t*)msg->data;
-
-		for (i=0; i< msgs_max -1; i++) {
-			if ( (hh->prim&COMMAND_MASK)==(msgs[i].misdn_msg&COMMAND_MASK)) return i;
-		}
-
-	} else {
-		iframe_t *frm = (iframe_t*)msg->data;
-
-		for (i=0; i< msgs_max -1; i++)
-			if ( (frm->prim&COMMAND_MASK)==(msgs[i].misdn_msg&COMMAND_MASK)) return i;
-	}
-
-	return -1;
-}
-
-int isdn_msg_get_index_by_event(struct isdn_msg msgs[], enum event_e event, int nt)
-{
-	int i;
-	for (i=0; i< msgs_max; i++)
-		if ( event == msgs[i].event) return i;
-
-	cb_log(10,0, "get_index: event not found!\n");
-
-	return -1;
-}
-
-enum event_e isdn_msg_get_event(struct isdn_msg msgs[], msg_t *msg, int nt)
-{
-	int i=isdn_msg_get_index(msgs, msg, nt);
-	if(i>=0) return msgs[i].event;
-	return EVENT_UNKNOWN;
-}
-
-char * isdn_msg_get_info(struct isdn_msg msgs[], msg_t *msg, int nt)
-{
-	int i=isdn_msg_get_index(msgs, msg, nt);
-	if(i>=0) return msgs[i].info;
-	return NULL;
-}
-
-
-char EVENT_CLEAN_INFO[] = "CLEAN_UP";
-char EVENT_DTMF_TONE_INFO[] = "DTMF_TONE";
-char EVENT_NEW_L3ID_INFO[] = "NEW_L3ID";
-char EVENT_NEW_BC_INFO[] = "NEW_BC";
-char EVENT_PORT_ALARM_INFO[] = "ALARM";
-char EVENT_NEW_CHANNEL_INFO[] = "NEW_CHANNEL";
-char EVENT_BCHAN_DATA_INFO[] = "BCHAN_DATA";
-char EVENT_BCHAN_ACTIVATED_INFO[] = "BCHAN_ACTIVATED";
-char EVENT_TONE_GENERATE_INFO[] = "TONE_GENERATE";
-char EVENT_BCHAN_ERROR_INFO[] = "BCHAN_ERROR";
-
-char * isdn_get_info(struct isdn_msg msgs[], enum event_e event, int nt)
-{
-	int i=isdn_msg_get_index_by_event(msgs, event, nt);
-
-	if(i>=0) return msgs[i].info;
-
-	if (event == EVENT_CLEANUP) return EVENT_CLEAN_INFO;
-	if (event == EVENT_DTMF_TONE) return EVENT_DTMF_TONE_INFO;
-	if (event == EVENT_NEW_L3ID) return EVENT_NEW_L3ID_INFO;
-	if (event == EVENT_NEW_BC) return EVENT_NEW_BC_INFO;
-	if (event == EVENT_NEW_CHANNEL) return EVENT_NEW_CHANNEL_INFO;
-	if (event == EVENT_BCHAN_DATA) return EVENT_BCHAN_DATA_INFO;
-	if (event == EVENT_BCHAN_ACTIVATED) return EVENT_BCHAN_ACTIVATED_INFO;
-	if (event == EVENT_TONE_GENERATE) return EVENT_TONE_GENERATE_INFO;
-	if (event == EVENT_PORT_ALARM) return EVENT_PORT_ALARM_INFO;
-	if (event == EVENT_BCHAN_ERROR) return EVENT_BCHAN_ERROR_INFO;
-
-	return NULL;
-}
-
-int isdn_msg_parse_event(struct isdn_msg msgs[], msg_t *msg, struct misdn_bchannel *bc, int nt)
-{
-	int i=isdn_msg_get_index(msgs, msg, nt);
-	if(i<0) return -1;
-
-	msgs[i].msg_parser(msgs, msg, bc, nt);
-	return 0;
-}
-
-msg_t * isdn_msg_build_event(struct isdn_msg msgs[], struct misdn_bchannel *bc, enum event_e event, int nt)
-{
-	int i=isdn_msg_get_index_by_event(msgs, event, nt);
-	if(i<0) return NULL;
-
-	return  msgs[i].msg_builder(msgs, bc, nt);
-}
diff --git a/channels/misdn/portinfo.c b/channels/misdn/portinfo.c
deleted file mode 100644
index f6af3982a413659b5e6df00f8aa10a1fae475018..0000000000000000000000000000000000000000
--- a/channels/misdn/portinfo.c
+++ /dev/null
@@ -1,205 +0,0 @@
-/*! \file
- * \brief Interface to mISDN - port info
- * \author Christian Richter <crich@beronet.com>
- */
-
-/*** MODULEINFO
-	<support_level>extended</support_level>
- ***/
-
-#include "isdn_lib.h"
-#include "isdn_lib_intern.h"
-
-
-/*
- * global function to show all available isdn ports
- */
-void isdn_port_info(void)
-{
-	int err;
-	int i, ii, p;
-	int useable, nt, pri;
-	unsigned char buff[1025];
-	iframe_t *frm = (iframe_t *)buff;
-	stack_info_t *stinf;
-	int device;
-
-	/* open mISDN */
-	if ((device = mISDN_open()) < 0)
-	{
-		fprintf(stderr, "mISDN_open() failed: ret=%d errno=%d (%s) Check for mISDN modules and device.\n", device, errno, strerror(errno));
-		exit(-1);
-	}
-
-	/* get number of stacks */
-	i = 1;
-	ii = mISDN_get_stack_count(device);
-	printf("\n");
-	if (ii <= 0)
-	{
-		printf("Found no card. Please be sure to load card drivers.\n");
-	}
-
-	/* loop the number of cards and get their info */
-	while(i <= ii)
-	{
-		err = mISDN_get_stack_info(device, i, buff, sizeof(buff));
-		if (err <= 0)
-		{
-			fprintf(stderr, "mISDN_get_stack_info() failed: port=%d err=%d\n", i, err);
-			break;
-		}
-		stinf = (stack_info_t *)&frm->data.p;
-
-		nt = pri = 0;
-		useable = 1;
-
-		/* output the port info */
-		printf("Port %2d: ", i);
-		switch(stinf->pid.protocol[0] & ~ISDN_PID_FEATURE_MASK)
-		{
-			case ISDN_PID_L0_TE_S0:
-			printf("TE-mode BRI S/T interface line (for phone lines)");
-#if 0
-			if (stinf->pid.protocol[0] & ISDN_PID_L0_TE_S0_HFC & ISDN_PID_FEATURE_MASK)
-				printf(" HFC multiport card");
-#endif
-			break;
-			case ISDN_PID_L0_NT_S0:
-			nt = 1;
-			printf("NT-mode BRI S/T interface port (for phones)");
-#if 0
-			if (stinf->pid.protocol[0] & ISDN_PID_L0_NT_S0_HFC & ISDN_PID_FEATURE_MASK)
-				printf(" HFC multiport card");
-#endif
-			break;
-			case ISDN_PID_L0_TE_U:
-			printf("TE-mode BRI U   interface line");
-			break;
-			case ISDN_PID_L0_NT_U:
-			nt = 1;
-			printf("NT-mode BRI U   interface port");
-			break;
-			case ISDN_PID_L0_TE_UP2:
-			printf("TE-mode BRI Up2 interface line");
-			break;
-			case ISDN_PID_L0_NT_UP2:
-			nt = 1;
-			printf("NT-mode BRI Up2 interface port");
-			break;
-			case ISDN_PID_L0_TE_E1:
-			pri = 1;
-			printf("TE-mode PRI E1  interface line (for phone lines)");
-#if 0
-			if (stinf->pid.protocol[0] & ISDN_PID_L0_TE_E1_HFC & ISDN_PID_FEATURE_MASK)
-				printf(" HFC-E1 card");
-#endif
-			break;
-			case ISDN_PID_L0_NT_E1:
-			nt = 1;
-			pri = 1;
-			printf("NT-mode PRI E1  interface port (for phones)");
-#if 0
-			if (stinf->pid.protocol[0] & ISDN_PID_L0_NT_E1_HFC & ISDN_PID_FEATURE_MASK)
-				printf(" HFC-E1 card");
-#endif
-			break;
-			default:
-			useable = 0;
-			printf("unknown type 0x%08x",stinf->pid.protocol[0]);
-		}
-		printf("\n");
-
-		if (nt)
-		{
-			if (stinf->pid.protocol[1] == 0)
-			{
-				useable = 0;
-				printf(" -> Missing layer 1 NT-mode protocol.\n");
-			}
-			p = 2;
-			while(p <= MAX_LAYER_NR) {
-				if (stinf->pid.protocol[p])
-				{
-					useable = 0;
-					printf(" -> Layer %d protocol 0x%08x is detected, but not allowed for NT lib.\n", p, stinf->pid.protocol[p]);
-				}
-				p++;
-			}
-			if (useable)
-			{
-				if (pri)
-					printf(" -> Interface is Point-To-Point (PRI).\n");
-				else
-					printf(" -> Interface can be Poin-To-Point/Multipoint.\n");
-			}
-		} else
-		{
-			if (stinf->pid.protocol[1] == 0)
-			{
-				useable = 0;
-				printf(" -> Missing layer 1 protocol.\n");
-			}
-			if (stinf->pid.protocol[2] == 0)
-			{
-				useable = 0;
-				printf(" -> Missing layer 2 protocol.\n");
-			}
-			if (stinf->pid.protocol[2] & ISDN_PID_L2_DF_PTP)
-			{
-				printf(" -> Interface is Poin-To-Point.\n");
-			}
-			if (stinf->pid.protocol[3] == 0)
-			{
-				useable = 0;
-				printf(" -> Missing layer 3 protocol.\n");
-			} else
-			{
-				printf(" -> Protocol: ");
-				switch(stinf->pid.protocol[3] & ~ISDN_PID_FEATURE_MASK)
-				{
-					case ISDN_PID_L3_DSS1USER:
-					printf("DSS1 (Euro ISDN)");
-					break;
-
-					default:
-					useable = 0;
-					printf("unknown protocol 0x%08x",stinf->pid.protocol[3]);
-				}
-				printf("\n");
-			}
-			p = 4;
-			while(p <= MAX_LAYER_NR) {
-				if (stinf->pid.protocol[p])
-				{
-					useable = 0;
-					printf(" -> Layer %d protocol 0x%08x is detected, but not allowed for TE lib.\n", p, stinf->pid.protocol[p]);
-				}
-				p++;
-			}
-			printf(" -> childcnt: %d\n",stinf->childcnt);
-		}
-
-		if (!useable)
-			printf(" * Port NOT useable for PBX\n");
-
-		printf("--------\n");
-
-		i++;
-	}
-	printf("\n");
-
-	/* close mISDN */
-	if ((err = mISDN_close(device)))
-	{
-		fprintf(stderr, "mISDN_close() failed: err=%d '%s'\n", err, strerror(err));
-		exit(-1);
-	}
-}
-
-
-int main()
-{
-  isdn_port_info();
-  return 0;
-}
diff --git a/channels/misdn_config.c b/channels/misdn_config.c
deleted file mode 100644
index bfb3e32ea94d19b83b4221a011812ae94245609f..0000000000000000000000000000000000000000
--- a/channels/misdn_config.c
+++ /dev/null
@@ -1,1273 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2005, Christian Richter
- *
- * Christian Richter <crich@beronet.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- *
- */
-
-/*!
- * \file
- *
- * \brief chan_misdn configuration management
- * \author Christian Richter <crich@beronet.com>
- *
- * \ingroup channel_drivers
- */
-
-/*** MODULEINFO
-	<support_level>extended</support_level>
- ***/
-
-#include "asterisk.h"
-
-#include "chan_misdn_config.h"
-
-#include "asterisk/config.h"
-#include "asterisk/channel.h"
-#include "asterisk/lock.h"
-#include "asterisk/pbx.h"
-#include "asterisk/strings.h"
-#include "asterisk/utils.h"
-
-#define NO_DEFAULT "<>"
-#define NONE 0
-
-#define GEN_CFG 1
-#define PORT_CFG 2
-#define NUM_GEN_ELEMENTS (sizeof(gen_spec) / sizeof(struct misdn_cfg_spec))
-#define NUM_PORT_ELEMENTS (sizeof(port_spec) / sizeof(struct misdn_cfg_spec))
-
-/*! Global jitterbuffer configuration - by default, jb is disabled
- *  \note Values shown here match the defaults shown in misdn.conf.sample */
-static struct ast_jb_conf default_jbconf =
-{
-	.flags = 0,
-	.max_size = 200,
-	.resync_threshold = 1000,
-	.impl = "fixed",
-	.target_extra = 40,
-};
-
-static struct ast_jb_conf global_jbconf;
-
-enum misdn_cfg_type {
-	MISDN_CTYPE_STR,
-	MISDN_CTYPE_INT,
-	MISDN_CTYPE_BOOL,
-	MISDN_CTYPE_BOOLINT,
-	MISDN_CTYPE_MSNLIST,
-	MISDN_CTYPE_ASTGROUP,
-	MISDN_CTYPE_ASTNAMEDGROUP
-};
-
-struct msn_list {
-	char *msn;
-	struct msn_list *next;
-};
-
-union misdn_cfg_pt {
-	char *str;
-	int *num;
-	struct msn_list *ml;
-	ast_group_t *grp;
-	struct ast_namedgroups *namgrp;
-	void *any;
-};
-
-struct misdn_cfg_spec {
-	char name[BUFFERSIZE];
-	enum misdn_cfg_elements elem;
-	enum misdn_cfg_type type;
-	char def[BUFFERSIZE];
-	int boolint_def;
-	char desc[BUFFERSIZE];
-};
-
-
-static const char ports_description[] =
-	"Define your ports, e.g. 1,2 (depends on mISDN-driver loading order).";
-
-static const struct misdn_cfg_spec port_spec[] = {
-	{ "name", MISDN_CFG_GROUPNAME, MISDN_CTYPE_STR, "default", NONE,
-		"Name of the portgroup." },
-	{ "allowed_bearers", MISDN_CFG_ALLOWED_BEARERS, MISDN_CTYPE_STR, "all", NONE,
-		"Here you can list which bearer capabilities should be allowed:\n"
-		"\t  all                  - allow any bearer capability\n"
-		"\t  speech               - allow speech\n"
-		"\t  3_1khz               - allow 3.1KHz audio\n"
-		"\t  digital_unrestricted - allow unrestricted digital\n"
-		"\t  digital_restricted   - allow restricted digital\n"
-		"\t  video                - allow video" },
-	{ "rxgain", MISDN_CFG_RXGAIN, MISDN_CTYPE_INT, "0", NONE,
-		"Set this between -8 and 8 to change the RX Gain." },
-	{ "txgain", MISDN_CFG_TXGAIN, MISDN_CTYPE_INT, "0", NONE,
-		"Set this between -8 and 8 to change the TX Gain." },
-	{ "te_choose_channel", MISDN_CFG_TE_CHOOSE_CHANNEL, MISDN_CTYPE_BOOL, "no", NONE,
-		"Some telcos especially in NL seem to need this set to yes,\n"
-		"\talso in Switzerland this seems to be important." },
-	{ "far_alerting", MISDN_CFG_FAR_ALERTING, MISDN_CTYPE_BOOL, "no", NONE,
-		"If we should generate ringing for chan_sip and others." },
-	{ "pmp_l1_check", MISDN_CFG_PMP_L1_CHECK, MISDN_CTYPE_BOOL, "no", NONE,
-		"This option defines, if chan_misdn should check the L1 on a PMP\n"
-		"\tbefore making a group call on it. The L1 may go down for PMP Ports\n"
-		"\tso we might need this.\n"
-		"\tBut be aware! a broken or plugged off cable might be used for a group call\n"
-		"\tas well, since chan_misdn has no chance to distinguish if the L1 is down\n"
-		"\tbecause of a lost Link or because the Provider shut it down..." },
-	{ "block_on_alarm", MISDN_CFG_ALARM_BLOCK, MISDN_CTYPE_BOOL, "no", NONE ,
-	  "Block this port if we have an alarm on it." },
-	{ "hdlc", MISDN_CFG_HDLC, MISDN_CTYPE_BOOL, "no", NONE,
-		"Set this to yes, if you want to bridge a mISDN data channel to\n"
-		"\tanother channel type or to an application." },
-	{ "context", MISDN_CFG_CONTEXT, MISDN_CTYPE_STR, "default", NONE,
-		"Context to use for incoming calls." },
-	{ "language", MISDN_CFG_LANGUAGE, MISDN_CTYPE_STR, "en", NONE,
-		"Language." },
-	{ "musicclass", MISDN_CFG_MUSICCLASS, MISDN_CTYPE_STR, "default", NONE,
-		"Sets the musiconhold class." },
-	{ "callerid", MISDN_CFG_CALLERID, MISDN_CTYPE_STR, "", NONE,
-		"Set the outgoing caller id to the value." },
-	{ "incoming_cid_tag", MISDN_CFG_INCOMING_CALLERID_TAG, MISDN_CTYPE_STR, "", NONE,
-		"Set the incoming caller id string tag to the value." },
-	{ "append_msn_to_cid_tag", MISDN_CFG_APPEND_MSN_TO_CALLERID_TAG, MISDN_CTYPE_BOOL, "no", NONE,
-		"Automatically appends incoming or outgoing MSN to the incoming caller\n"
-		"\tid string tag. An underscore '_' is used as delimiter. Incoming calls\n"
-		"\twill have the dialed number appended, and outgoing calls will have the\n"
-		"\tcaller number appended to the tag." },
-	{ "method", MISDN_CFG_METHOD, MISDN_CTYPE_STR, "standard", NONE,
-		"Set the method to use for channel selection:\n"
-		"\t  standard     - Use the first free channel starting from the lowest number.\n"
-		"\t  standard_dec - Use the first free channel starting from the highest number.\n"
-		"\t  round_robin  - Use the round robin algorithm to select a channel. Use this\n"
-		"\t                 if you want to balance your load." },
-	{ "dialplan", MISDN_CFG_DIALPLAN, MISDN_CTYPE_INT, "0", NONE,
-		"Dialplan means Type Of Number in ISDN Terms\n"
-		"\tThere are different types of the dialplan:\n"
-		"\n"
-		"\tdialplan -> for outgoing call's dialed number\n"
-		"\tlocaldialplan -> for outgoing call's callerid\n"
-		"\t      (if -1 is set use the value from the asterisk channel)\n"
-		"\tcpndialplan -> for incoming call's connected party number sent to caller\n"
-		"\t      (if -1 is set use the value from the asterisk channel)\n"
-		"\n"
-		"\tdialplan options:\n"
-		"\n"
-		"\t0 - unknown\n"
-		"\t1 - International\n"
-		"\t2 - National\n"
-		"\t4 - Subscriber" },
-	{ "localdialplan", MISDN_CFG_LOCALDIALPLAN, MISDN_CTYPE_INT, "0", NONE,
-		"Dialplan means Type Of Number in ISDN Terms\n"
-		"\tThere are different types of the dialplan:\n"
-		"\n"
-		"\tdialplan -> for outgoing call's dialed number\n"
-		"\tlocaldialplan -> for outgoing call's callerid\n"
-		"\t      (if -1 is set use the value from the asterisk channel)\n"
-		"\tcpndialplan -> for incoming call's connected party number sent to caller\n"
-		"\t      (if -1 is set use the value from the asterisk channel)\n"
-		"\n"
-		"\tdialplan options:\n"
-		"\n"
-		"\t0 - unknown\n"
-		"\t1 - International\n"
-		"\t2 - National\n"
-		"\t4 - Subscriber" },
-	{ "cpndialplan", MISDN_CFG_CPNDIALPLAN, MISDN_CTYPE_INT, "0", NONE,
-		"Dialplan means Type Of Number in ISDN Terms\n"
-		"\tThere are different types of the dialplan:\n"
-		"\n"
-		"\tdialplan -> for outgoing call's dialed number\n"
-		"\tlocaldialplan -> for outgoing call's callerid\n"
-		"\t      (if -1 is set use the value from the asterisk channel)\n"
-		"\tcpndialplan -> for incoming call's connected party number sent to caller\n"
-		"\t      (if -1 is set use the value from the asterisk channel)\n"
-		"\n"
-		"\tdialplan options:\n"
-		"\n"
-		"\t0 - unknown\n"
-		"\t1 - International\n"
-		"\t2 - National\n"
-		"\t4 - Subscriber" },
-	{ "unknownprefix", MISDN_CFG_TON_PREFIX_UNKNOWN, MISDN_CTYPE_STR, "", NONE,
-		"Prefix for unknown numbers, this is put before an incoming number\n"
-		"\tif its type-of-number is unknown." },
-	{ "internationalprefix", MISDN_CFG_TON_PREFIX_INTERNATIONAL, MISDN_CTYPE_STR, "00", NONE,
-		"Prefix for international numbers, this is put before an incoming number\n"
-		"\tif its type-of-number is international." },
-	{ "nationalprefix", MISDN_CFG_TON_PREFIX_NATIONAL, MISDN_CTYPE_STR, "0", NONE,
-		"Prefix for national numbers, this is put before an incoming number\n"
-		"\tif its type-of-number is national." },
-	{ "netspecificprefix", MISDN_CFG_TON_PREFIX_NETWORK_SPECIFIC, MISDN_CTYPE_STR, "", NONE,
-		"Prefix for network-specific numbers, this is put before an incoming number\n"
-		"\tif its type-of-number is network-specific." },
-	{ "subscriberprefix", MISDN_CFG_TON_PREFIX_SUBSCRIBER, MISDN_CTYPE_STR, "", NONE,
-		"Prefix for subscriber numbers, this is put before an incoming number\n"
-		"\tif its type-of-number is subscriber." },
-	{ "abbreviatedprefix", MISDN_CFG_TON_PREFIX_ABBREVIATED, MISDN_CTYPE_STR, "", NONE,
-		"Prefix for abbreviated numbers, this is put before an incoming number\n"
-		"\tif its type-of-number is abbreviated." },
-	{ "presentation", MISDN_CFG_PRES, MISDN_CTYPE_INT, "-1", NONE,
-		"These (presentation and screen) are the exact isdn screening and presentation\n"
-		"\tindicators.\n"
-		"\tIf -1 is given for either value, the presentation indicators are used from\n"
-		"\tAsterisk's CALLERPRES function.\n"
-		"\n"
-		"\tscreen=0, presentation=0 -> callerid presented\n"
-		"\tscreen=1, presentation=1 -> callerid restricted (the remote end doesn't see it!)" },
-	{ "screen", MISDN_CFG_SCREEN, MISDN_CTYPE_INT, "-1", NONE,
-		"These (presentation and screen) are the exact isdn screening and presentation\n"
-		"\tindicators.\n"
-		"\tIf -1 is given for either value, the presentation indicators are used from\n"
-		"\tAsterisk's CALLERPRES function.\n"
-		"\n"
-		"\tscreen=0, presentation=0 -> callerid presented\n"
-		"\tscreen=1, presentation=1 -> callerid restricted (the remote end doesn't see it!)" },
-	{ "outgoing_colp", MISDN_CFG_OUTGOING_COLP, MISDN_CTYPE_INT, "0", NONE,
-		"Select what to do with outgoing COLP information on this port.\n"
-		"\n"
-		"\t0 - Send out COLP information unaltered.\n"
-		"\t1 - Force COLP to restricted on all outgoing COLP information.\n"
-		"\t2 - Do not send COLP information." },
-	{ "display_connected", MISDN_CFG_DISPLAY_CONNECTED, MISDN_CTYPE_INT, "0", NONE,
-		"Put a display ie in the CONNECT message containing the following\n"
-		"\tinformation if it is available (nt port only):\n"
-		"\n"
-		"\t0 - Do not put the connected line information in the display ie.\n"
-		"\t1 - Put the available connected line name in the display ie.\n"
-		"\t2 - Put the available connected line number in the display ie.\n"
-		"\t3 - Put the available connected line name and number in the display ie." },
-	{ "display_setup", MISDN_CFG_DISPLAY_SETUP, MISDN_CTYPE_INT, "0", NONE,
-		"Put a display ie in the SETUP message containing the following\n"
-		"\tinformation if it is available (nt port only):\n"
-		"\n"
-		"\t0 - Do not put the caller information in the display ie.\n"
-		"\t1 - Put the available caller name in the display ie.\n"
-		"\t2 - Put the available caller number in the display ie.\n"
-		"\t3 - Put the available caller name and number in the display ie." },
-	{ "always_immediate", MISDN_CFG_ALWAYS_IMMEDIATE, MISDN_CTYPE_BOOL, "no", NONE,
-		"Enable this to get into the s dialplan-extension.\n"
-		"\tThere you can use DigitTimeout if you can't or don't want to use\n"
-		"\tisdn overlap dial.\n"
-		"\tNOTE: This will jump into the s extension for every exten!" },
-	{ "nodialtone", MISDN_CFG_NODIALTONE, MISDN_CTYPE_BOOL, "no", NONE,
-		"Enable this to prevent chan_misdn to generate the dialtone\n"
-		"\tThis makes only sense together with the always_immediate=yes option\n"
-		"\tto generate your own dialtone with Playtones or so." },
-	{ "immediate", MISDN_CFG_IMMEDIATE, MISDN_CTYPE_BOOL, "no", NONE,
-		"Enable this if you want callers which called exactly the base\n"
-		"\tnumber (so no extension is set) to jump into the s extension.\n"
-		"\tIf the user dials something more, it jumps to the correct extension\n"
-		"\tinstead." },
-	{ "senddtmf", MISDN_CFG_SENDDTMF, MISDN_CTYPE_BOOL, "no", NONE,
-		"Enable this if we should produce DTMF Tones ourselves." },
-	{ "astdtmf", MISDN_CFG_ASTDTMF, MISDN_CTYPE_BOOL, "no", NONE,
-		"Enable this if you want to use the Asterisk dtmf detector\n"
-		"instead of the mISDN_dsp/hfcmulti one."
-		},
-	{ "hold_allowed", MISDN_CFG_HOLD_ALLOWED, MISDN_CTYPE_BOOL, "no", NONE,
-		"Enable this to have support for hold and retrieve." },
-	{ "early_bconnect", MISDN_CFG_EARLY_BCONNECT, MISDN_CTYPE_BOOL, "yes", NONE,
-		"Disable this if you don't mind correct handling of Progress Indicators." },
-	{ "incoming_early_audio", MISDN_CFG_INCOMING_EARLY_AUDIO, MISDN_CTYPE_BOOL, "no", NONE,
-		"Turn this on if you like to send Tone Indications to a Incoming\n"
-		"\tisdn channel on a TE Port. Rarely used, only if the Telco allows\n"
-		"\tyou to send indications by yourself, normally the Telco sends the\n"
-		"\tindications to the remote party." },
-	{ "echocancel", MISDN_CFG_ECHOCANCEL, MISDN_CTYPE_BOOLINT, "0", 128,
-		"This enables echo cancellation with the given number of taps.\n"
-		"\tBe aware: Move this setting only to outgoing portgroups!\n"
-		"\tA value of zero turns echo cancellation off.\n"
-		"\n"
-		"\tPossible values are: 0,32,64,128,256,yes(=128),no(=0)" },
-#ifdef MISDN_1_2
-	{ "pipeline", MISDN_CFG_PIPELINE, MISDN_CTYPE_STR, NO_DEFAULT, NONE,
-		"Set the configuration string for the mISDN dsp pipeline.\n"
-		"\n"
-		"\tExample for enabling the mg2 echo cancellation module with deftaps\n"
-		"\tset to 128:\n"
-		"\t\tmg2ec(deftaps=128)" },
-#endif
-#ifdef WITH_BEROEC
-	{ "bnechocancel", MISDN_CFG_BNECHOCANCEL, MISDN_CTYPE_BOOLINT, "yes", 64,
-		"echotail in ms (1-200)" },
-	{ "bnec_antihowl", MISDN_CFG_BNEC_ANTIHOWL, MISDN_CTYPE_INT, "0", NONE,
-		"Use antihowl" },
-	{ "bnec_nlp", MISDN_CFG_BNEC_NLP, MISDN_CTYPE_BOOL, "yes", NONE,
-		"Nonlinear Processing (much faster adaption)" },
-	{ "bnec_zerocoeff", MISDN_CFG_BNEC_ZEROCOEFF, MISDN_CTYPE_BOOL, "no", NONE,
-		"ZeroCoeffeciens" },
-	{ "bnec_tonedisabler", MISDN_CFG_BNEC_TD, MISDN_CTYPE_BOOL, "no", NONE,
-		"Disable Tone" },
-	{ "bnec_adaption", MISDN_CFG_BNEC_ADAPT, MISDN_CTYPE_INT, "1", NONE,
-		"Adaption mode (0=no,1=full,2=fast)" },
-#endif
-	{ "need_more_infos", MISDN_CFG_NEED_MORE_INFOS, MISDN_CTYPE_BOOL, "0", NONE,
-		"Send Setup_Acknowledge on incoming calls anyway (instead of PROCEEDING),\n"
-		"\tthis requests additional Infos, so we can waitfordigits without much\n"
-		"\tissues. This works only for PTP Ports" },
-	{ "noautorespond_on_setup", MISDN_CFG_NOAUTORESPOND_ON_SETUP, MISDN_CTYPE_BOOL, "0", NONE,
-		"Do not send SETUP_ACKNOWLEDGE or PROCEEDING automatically to the calling Party.\n"
-		"Instead we directly jump into the dialplan. This might be useful for fast call\n"
-		"rejection, or for some broken switches, that need hangup causes like busy in the.\n"
-		"RELEASE_COMPLETE Message, instead of the DISCONNECT Message."},
-	{ "jitterbuffer", MISDN_CFG_JITTERBUFFER, MISDN_CTYPE_INT, "4000", NONE,
-		"The jitterbuffer." },
-	{ "jitterbuffer_upper_threshold", MISDN_CFG_JITTERBUFFER_UPPER_THRESHOLD, MISDN_CTYPE_INT, "0", NONE,
-		"Change this threshold to enable dejitter functionality." },
-	{ "callgroup", MISDN_CFG_CALLGROUP, MISDN_CTYPE_ASTGROUP, NO_DEFAULT, NONE,
-		"Callgroup." },
-	{ "pickupgroup", MISDN_CFG_PICKUPGROUP, MISDN_CTYPE_ASTGROUP, NO_DEFAULT, NONE,
-		"Pickupgroup." },
-	{ "namedcallgroup", MISDN_CFG_NAMEDCALLGROUP, MISDN_CTYPE_ASTNAMEDGROUP, NO_DEFAULT, NONE,
-		"Named callgroup." },
-	{ "namedpickupgroup", MISDN_CFG_NAMEDPICKUPGROUP, MISDN_CTYPE_ASTNAMEDGROUP, NO_DEFAULT, NONE,
-		"Named pickupgroup." },
-	{ "max_incoming", MISDN_CFG_MAX_IN, MISDN_CTYPE_INT, "-1", NONE,
-		"Defines the maximum amount of incoming calls per port for this group.\n"
-		"\tCalls which exceed the maximum will be marked with the channel variable\n"
-		"\tMAX_OVERFLOW. It will contain the amount of overflowed calls" },
-	{ "max_outgoing", MISDN_CFG_MAX_OUT, MISDN_CTYPE_INT, "-1", NONE,
-		"Defines the maximum amount of outgoing calls per port for this group\n"
-		"\texceeding calls will be rejected" },
-
-	{ "reject_cause", MISDN_CFG_REJECT_CAUSE, MISDN_CTYPE_INT, "21", NONE,
-		"Defines the cause with which a 3. call is rejected on PTMP BRI."},
-	{ "faxdetect", MISDN_CFG_FAXDETECT, MISDN_CTYPE_STR, "no", NONE,
-		"Setup fax detection:\n"
-		"\t    no        - no fax detection\n"
-		"\t    incoming  - fax detection for incoming calls\n"
-		"\t    outgoing  - fax detection for outgoing calls\n"
-		"\t    both      - fax detection for incoming and outgoing calls\n"
-		"\tAdd +nojump to your value (i.e. faxdetect=both+nojump) if you don't want to jump into the\n"
-		"\tfax-extension but still want to detect the fax and prepare the channel for fax transfer." },
-	{ "faxdetect_timeout", MISDN_CFG_FAXDETECT_TIMEOUT, MISDN_CTYPE_INT, "5", NONE,
-		"Number of seconds the fax detection should do its job. After the given period of time,\n"
-		"\twe assume that it's not a fax call and save some CPU time by turning off fax detection.\n"
-		"\tSet this to 0 if you don't want a timeout (never stop detecting)." },
-	{ "faxdetect_context", MISDN_CFG_FAXDETECT_CONTEXT, MISDN_CTYPE_STR, NO_DEFAULT, NONE,
-		"Context to jump into if we detect a fax. Don't set this if you want to stay in the current context." },
-	{ "l1watcher_timeout", MISDN_CFG_L1_TIMEOUT, MISDN_CTYPE_BOOLINT, "0", 4,
-		"Monitors L1 of the port.  If L1 is down it tries\n"
-		"\tto bring it up.  The polling timeout is given in seconds.\n"
-		"\tSetting the value to 0 disables monitoring L1 of the port.\n"
-		"\n"
-		"\tThis option is only read at chan_misdn loading time.\n"
-		"\tYou need to unload and load chan_misdn to change the\n"
-		"\tvalue.  An asterisk restart will also do the trick." },
-	{ "overlapdial", MISDN_CFG_OVERLAP_DIAL, MISDN_CTYPE_BOOLINT, "0", 4,
-		"Enables overlap dial for the given amount of seconds.\n"
-		"\tPossible values are positive integers or:\n"
-		"\t   yes (= 4 seconds)\n"
-		"\t   no  (= 0 seconds = disabled)" },
-	{ "nttimeout", MISDN_CFG_NTTIMEOUT, MISDN_CTYPE_BOOL, "no", NONE ,
-		"Set this to yes if you want calls disconnected in overlap mode\n"
-		"\twhen a timeout happens." },
-	{ "bridging", MISDN_CFG_BRIDGING, MISDN_CTYPE_BOOL, "yes", NONE,
-	 	"Set this to yes/no, default is yes.\n"
-		"This can be used to have bridging enabled in general and to\n"
-		"disable it for specific ports. It makes sense to disable\n"
-		"bridging on NT Port where you plan to use the HOLD/RETRIEVE\n"
-		"features with ISDN phones." },
-	{ "msns", MISDN_CFG_MSNS, MISDN_CTYPE_MSNLIST, "*", NONE,
-		"MSN's for TE ports, listen on those numbers on the above ports, and\n"
-		"\tindicate the incoming calls to Asterisk.\n"
-		"\tHere you can give a comma separated list, or simply an '*' for any msn." },
-	{ "cc_request_retention", MISDN_CFG_CC_REQUEST_RETENTION, MISDN_CTYPE_BOOL, "yes", NONE,
-		"Enable/Disable call-completion request retention support (ptp)." },
-};
-
-static const struct misdn_cfg_spec gen_spec[] = {
-	{ "debug", MISDN_GEN_DEBUG, MISDN_CTYPE_INT, "0", NONE,
-		"Sets the debugging flag:\n"
-		"\t0 - No Debug\n"
-		"\t1 - mISDN Messages and * - Messages, and * - State changes\n"
-		"\t2 - Messages + Message specific Informations (e.g. bearer capability)\n"
-		"\t3 - very Verbose, the above + lots of Driver specific infos\n"
-		"\t4 - even more Verbose than 3" },
-#ifndef MISDN_1_2
-	{ "misdn_init", MISDN_GEN_MISDN_INIT, MISDN_CTYPE_STR, "/etc/misdn-init.conf", NONE,
-		"Set the path to the misdn-init.conf (for nt_ptp mode checking)." },
-#endif
-	{ "tracefile", MISDN_GEN_TRACEFILE, MISDN_CTYPE_STR, "/var/log/asterisk/misdn.log", NONE,
-		"Set the path to the massively growing trace file, if you want that." },
-	{ "bridging", MISDN_GEN_BRIDGING, MISDN_CTYPE_BOOL, "yes", NONE,
-		"Set this to yes if you want mISDN_dsp to bridge the calls in HW." },
-	{ "stop_tone_after_first_digit", MISDN_GEN_STOP_TONE, MISDN_CTYPE_BOOL, "yes", NONE,
-		"Stops dialtone after getting first digit on NT Port." },
-	{ "append_digits2exten", MISDN_GEN_APPEND_DIGITS2EXTEN, MISDN_CTYPE_BOOL, "yes", NONE,
-		"Whether to append overlapdialed Digits to Extension or not." },
-	{ "dynamic_crypt", MISDN_GEN_DYNAMIC_CRYPT, MISDN_CTYPE_BOOL, "no", NONE,
-		"Whether to look out for dynamic crypting attempts." },
-	{ "crypt_prefix", MISDN_GEN_CRYPT_PREFIX, MISDN_CTYPE_STR, NO_DEFAULT, NONE,
-		"What is used for crypting Protocol." },
-	{ "crypt_keys", MISDN_GEN_CRYPT_KEYS, MISDN_CTYPE_STR, NO_DEFAULT, NONE,
-		"Keys for cryption, you reference them in the dialplan\n"
-		"\tLater also in dynamic encr." },
- 	{ "ntkeepcalls", MISDN_GEN_NTKEEPCALLS, MISDN_CTYPE_BOOL, "no", NONE,
-		"avoid dropping calls if the L2 goes down. some Nortel pbx\n"
-		"do put down the L2/L1 for some milliseconds even if there\n"
-		"are running calls. with this option you can avoid dropping them" },
-	{ "ntdebugflags", MISDN_GEN_NTDEBUGFLAGS, MISDN_CTYPE_INT, "0", NONE,
-	  	"No description yet."},
-	{ "ntdebugfile", MISDN_GEN_NTDEBUGFILE, MISDN_CTYPE_STR, "/var/log/misdn-nt.log", NONE,
-	  	"No description yet." }
-};
-
-
-/* array of port configs, default is at position 0. */
-static union misdn_cfg_pt **port_cfg;
-/* max number of available ports, is set on init */
-static int max_ports;
-/* general config */
-static union misdn_cfg_pt *general_cfg;
-/* storing the ptp flag separated to save memory */
-static int *ptp;
-/* maps enum config elements to array positions */
-static int *map;
-
-static ast_mutex_t config_mutex;
-
-#define CLI_ERROR(name, value, section) ({ \
-	ast_log(LOG_WARNING, "misdn.conf: \"%s=%s\" (section: %s) invalid or out of range. " \
-		"Please edit your misdn.conf and then do a \"misdn reload\".\n", name, value, section); \
-})
-
-static int _enum_array_map (void)
-{
-	int i, j, ok;
-
-	for (i = MISDN_CFG_FIRST + 1; i < MISDN_CFG_LAST; ++i) {
-		if (i == MISDN_CFG_PTP)
-			continue;
-		ok = 0;
-		for (j = 0; j < NUM_PORT_ELEMENTS; ++j) {
-			if (port_spec[j].elem == i) {
-				map[i] = j;
-				ok = 1;
-				break;
-			}
-		}
-		if (!ok) {
-			ast_log(LOG_WARNING, "Enum element %d in misdn_cfg_elements (port section) has no corresponding element in the config struct!\n", i);
-			return -1;
-		}
-	}
-	for (i = MISDN_GEN_FIRST + 1; i < MISDN_GEN_LAST; ++i) {
-		ok = 0;
-		for (j = 0; j < NUM_GEN_ELEMENTS; ++j) {
-			if (gen_spec[j].elem == i) {
-				map[i] = j;
-				ok = 1;
-				break;
-			}
-		}
-		if (!ok) {
-			ast_log(LOG_WARNING, "Enum element %d in misdn_cfg_elements (general section) has no corresponding element in the config struct!\n", i);
-			return -1;
-		}
-	}
-	return 0;
-}
-
-static int get_cfg_position (const char *name, int type)
-{
-	int i;
-
-	switch (type) {
-	case PORT_CFG:
-		for (i = 0; i < NUM_PORT_ELEMENTS; ++i) {
-			if (!strcasecmp(name, port_spec[i].name))
-				return i;
-		}
-		break;
-	case GEN_CFG:
-		for (i = 0; i < NUM_GEN_ELEMENTS; ++i) {
-			if (!strcasecmp(name, gen_spec[i].name))
-				return i;
-		}
-	}
-
-	return -1;
-}
-
-static inline void misdn_cfg_lock (void)
-{
-	ast_mutex_lock(&config_mutex);
-}
-
-static inline void misdn_cfg_unlock (void)
-{
-	ast_mutex_unlock(&config_mutex);
-}
-
-static void _free_msn_list (struct msn_list* iter)
-{
-	if (iter->next)
-		_free_msn_list(iter->next);
-	if (iter->msn)
-		ast_free(iter->msn);
-	ast_free(iter);
-}
-
-static void _free_port_cfg (void)
-{
-	int i, j;
-	int gn = map[MISDN_CFG_GROUPNAME];
-	union misdn_cfg_pt* free_list[max_ports + 2];
-
-	memset(free_list, 0, sizeof(free_list));
-	free_list[0] = port_cfg[0];
-	for (i = 1; i <= max_ports; ++i) {
-		if (port_cfg[i][gn].str) {
-			/* we always have a groupname in the non-default case, so this is fine */
-			for (j = 1; j <= max_ports; ++j) {
-				if (free_list[j] && free_list[j][gn].str == port_cfg[i][gn].str)
-					break;
-				else if (!free_list[j]) {
-					free_list[j] = port_cfg[i];
-					break;
-				}
-			}
-		}
-	}
-	for (j = 0; free_list[j]; ++j) {
-		for (i = 0; i < NUM_PORT_ELEMENTS; ++i) {
-			if (free_list[j][i].any) {
-				if (port_spec[i].type == MISDN_CTYPE_MSNLIST) {
-					_free_msn_list(free_list[j][i].ml);
-				} else if (port_spec[i].type == MISDN_CTYPE_ASTNAMEDGROUP) {
-					ast_unref_namedgroups(free_list[j][i].namgrp);
-				} else {
-					ast_free(free_list[j][i].any);
-				}
-			}
-		}
-	}
-}
-
-static void _free_general_cfg (void)
-{
-	int i;
-
-	for (i = 0; i < NUM_GEN_ELEMENTS; i++)
-		if (general_cfg[i].any)
-			ast_free(general_cfg[i].any);
-}
-
-void misdn_cfg_get(int port, enum misdn_cfg_elements elem, void *buf, int bufsize)
-{
-	int place;
-
-	if ((elem < MISDN_CFG_LAST) && !misdn_cfg_is_port_valid(port)) {
-		memset(buf, 0, bufsize);
-		ast_log(LOG_WARNING, "Invalid call to misdn_cfg_get! Port number %d is not valid.\n", port);
-		return;
-	}
-
-	misdn_cfg_lock();
-	if (elem == MISDN_CFG_PTP) {
-		if (!memcpy(buf, &ptp[port], (bufsize > ptp[port]) ? sizeof(ptp[port]) : bufsize))
-			memset(buf, 0, bufsize);
-	} else {
-		if ((place = map[elem]) < 0) {
-			memset(buf, 0, bufsize);
-			ast_log(LOG_WARNING, "Invalid call to misdn_cfg_get! Invalid element (%d) requested.\n", elem);
-		} else {
-			if (elem < MISDN_CFG_LAST) {
-				switch (port_spec[place].type) {
-				case MISDN_CTYPE_STR:
-					if (port_cfg[port][place].str) {
-						ast_copy_string(buf, port_cfg[port][place].str, bufsize);
-					} else if (port_cfg[0][place].str) {
-						ast_copy_string(buf, port_cfg[0][place].str, bufsize);
-					} else
-						memset(buf, 0, bufsize);
-					break;
-				case MISDN_CTYPE_ASTNAMEDGROUP:
-					if (bufsize >= sizeof(struct ast_namedgroups *)) {
-						if (port_cfg[port][place].namgrp) {
-							*(struct ast_namedgroups **)buf = port_cfg[port][place].namgrp;
-						} else if (port_cfg[0][place].namgrp) {
-							*(struct ast_namedgroups **)buf = port_cfg[0][place].namgrp;
-						} else {
-							*(struct ast_namedgroups **)buf = NULL;
-						}
-					}
-					break;
-				default:
-					if (port_cfg[port][place].any)
-						memcpy(buf, port_cfg[port][place].any, bufsize);
-					else if (port_cfg[0][place].any)
-						memcpy(buf, port_cfg[0][place].any, bufsize);
-					else
-						memset(buf, 0, bufsize);
-				}
-			} else {
-				switch (gen_spec[place].type) {
-				case MISDN_CTYPE_STR:
-					ast_copy_string(buf, S_OR(general_cfg[place].str, ""), bufsize);
-					break;
-				default:
-					if (general_cfg[place].any)
-						memcpy(buf, general_cfg[place].any, bufsize);
-					else
-						memset(buf, 0, bufsize);
-				}
-			}
-		}
-	}
-	misdn_cfg_unlock();
-}
-
-enum misdn_cfg_elements misdn_cfg_get_elem(const char *name)
-{
-	int pos;
-
-	/* here comes a hack to replace the (not existing) "name" element with the "ports" element */
-	if (!strcmp(name, "ports"))
-		return MISDN_CFG_GROUPNAME;
-	if (!strcmp(name, "name"))
-		return MISDN_CFG_FIRST;
-
-	pos = get_cfg_position(name, PORT_CFG);
-	if (pos >= 0)
-		return port_spec[pos].elem;
-
-	pos = get_cfg_position(name, GEN_CFG);
-	if (pos >= 0)
-		return gen_spec[pos].elem;
-
-	return MISDN_CFG_FIRST;
-}
-
-void misdn_cfg_get_name(enum misdn_cfg_elements elem, void *buf, int bufsize)
-{
-	struct misdn_cfg_spec *spec = NULL;
-	int place = map[elem];
-
-	/* the ptp hack */
-	if (elem == MISDN_CFG_PTP) {
-		memset(buf, 0, 1);
-		return;
-	}
-
-	/* here comes a hack to replace the (not existing) "name" element with the "ports" element */
-	if (elem == MISDN_CFG_GROUPNAME) {
-		if (!snprintf(buf, bufsize, "ports"))
-			memset(buf, 0, 1);
-		return;
-	}
-
-	if ((elem > MISDN_CFG_FIRST) && (elem < MISDN_CFG_LAST))
-		spec = (struct misdn_cfg_spec *)port_spec;
-	else if ((elem > MISDN_GEN_FIRST) && (elem < MISDN_GEN_LAST))
-		spec = (struct misdn_cfg_spec *)gen_spec;
-
-	ast_copy_string(buf, spec ? spec[place].name : "", bufsize);
-}
-
-void misdn_cfg_get_desc (enum misdn_cfg_elements elem, void *buf, int bufsize, void *buf_default, int bufsize_default)
-{
-	int place = map[elem];
-	struct misdn_cfg_spec *spec = NULL;
-
-	/* here comes a hack to replace the (not existing) "name" element with the "ports" element */
-	if (elem == MISDN_CFG_GROUPNAME) {
-		ast_copy_string(buf, ports_description, bufsize);
-		if (buf_default && bufsize_default)
-			memset(buf_default, 0, 1);
-		return;
-	}
-
-	if ((elem > MISDN_CFG_FIRST) && (elem < MISDN_CFG_LAST))
-		spec = (struct misdn_cfg_spec *)port_spec;
-	else if ((elem > MISDN_GEN_FIRST) && (elem < MISDN_GEN_LAST))
-		spec = (struct misdn_cfg_spec *)gen_spec;
-
-	if (!spec)
-		memset(buf, 0, 1);
-	else {
-		ast_copy_string(buf, spec[place].desc, bufsize);
-		if (buf_default && bufsize) {
-			if (!strcmp(spec[place].def, NO_DEFAULT))
-				memset(buf_default, 0, 1);
-			else
-				ast_copy_string(buf_default, spec[place].def, bufsize_default);
-		}
-	}
-}
-
-int misdn_cfg_is_msn_valid (int port, char* msn)
-{
-	int re = 0;
-	struct msn_list *iter;
-
-	if (!misdn_cfg_is_port_valid(port)) {
-		ast_log(LOG_WARNING, "Invalid call to misdn_cfg_is_msn_valid! Port number %d is not valid.\n", port);
-		return 0;
-	}
-
-	misdn_cfg_lock();
-	if (port_cfg[port][map[MISDN_CFG_MSNS]].ml)
-		iter = port_cfg[port][map[MISDN_CFG_MSNS]].ml;
-	else
-		iter = port_cfg[0][map[MISDN_CFG_MSNS]].ml;
-	for (; iter; iter = iter->next)
-		if (*(iter->msn) == '*' || ast_extension_match(iter->msn, msn)) {
-			re = 1;
-			break;
-		}
-	misdn_cfg_unlock();
-
-	return re;
-}
-
-int misdn_cfg_is_port_valid (int port)
-{
-	int gn = map[MISDN_CFG_GROUPNAME];
-
-	return (port >= 1 && port <= max_ports && port_cfg[port][gn].str);
-}
-
-int misdn_cfg_is_group_method (char *group, enum misdn_cfg_method meth)
-{
-	int i, re = 0;
-	char *method ;
-
-	misdn_cfg_lock();
-
-	method = port_cfg[0][map[MISDN_CFG_METHOD]].str;
-
-	for (i = 1; i <= max_ports; i++) {
-		if (port_cfg[i] && port_cfg[i][map[MISDN_CFG_GROUPNAME]].str) {
-			if (!strcasecmp(port_cfg[i][map[MISDN_CFG_GROUPNAME]].str, group))
-				method = (port_cfg[i][map[MISDN_CFG_METHOD]].str ?
-						  port_cfg[i][map[MISDN_CFG_METHOD]].str : port_cfg[0][map[MISDN_CFG_METHOD]].str);
-		}
-	}
-
-	if (method) {
-		switch (meth) {
-		case METHOD_STANDARD:		re = !strcasecmp(method, "standard");
-									break;
-		case METHOD_ROUND_ROBIN:	re = !strcasecmp(method, "round_robin");
-									break;
-		case METHOD_STANDARD_DEC:	re = !strcasecmp(method, "standard_dec");
-									break;
-		}
-	}
-	misdn_cfg_unlock();
-
-	return re;
-}
-
-/*!
- * \brief Generate a comma separated list of all active ports
- */
-void misdn_cfg_get_ports_string (char *ports)
-{
-	char tmp[16];
-	int l, i;
-	int gn = map[MISDN_CFG_GROUPNAME];
-
-	*ports = 0;
-
-	misdn_cfg_lock();
-	for (i = 1; i <= max_ports; i++) {
-		if (port_cfg[i][gn].str) {
-			if (ptp[i])
-				sprintf(tmp, "%dptp,", i);
-			else
-				sprintf(tmp, "%d,", i);
-			strcat(ports, tmp);
-		}
-	}
-	misdn_cfg_unlock();
-
-	if ((l = strlen(ports))) {
-		/* Strip trailing ',' */
-		ports[l-1] = 0;
-	}
-}
-
-void misdn_cfg_get_config_string (int port, enum misdn_cfg_elements elem, char* buf, int bufsize)
-{
-	int place;
-	char tempbuf[BUFFERSIZE] = "";
-	struct msn_list *iter;
-
-	if ((elem < MISDN_CFG_LAST) && !misdn_cfg_is_port_valid(port)) {
-		*buf = 0;
-		ast_log(LOG_WARNING, "Invalid call to misdn_cfg_get_config_string! Port number %d is not valid.\n", port);
-		return;
-	}
-
-	place = map[elem];
-
-	misdn_cfg_lock();
-	if (elem == MISDN_CFG_PTP) {
-		snprintf(buf, bufsize, " -> ptp: %s", ptp[port] ? "yes" : "no");
-	}
-	else if (elem > MISDN_CFG_FIRST && elem < MISDN_CFG_LAST) {
-		switch (port_spec[place].type) {
-		case MISDN_CTYPE_INT:
-		case MISDN_CTYPE_BOOLINT:
-			if (port_cfg[port][place].num)
-				snprintf(buf, bufsize, " -> %s: %d", port_spec[place].name, *port_cfg[port][place].num);
-			else if (port_cfg[0][place].num)
-				snprintf(buf, bufsize, " -> %s: %d", port_spec[place].name, *port_cfg[0][place].num);
-			else
-				snprintf(buf, bufsize, " -> %s:", port_spec[place].name);
-			break;
-		case MISDN_CTYPE_BOOL:
-			if (port_cfg[port][place].num)
-				snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name, *port_cfg[port][place].num ? "yes" : "no");
-			else if (port_cfg[0][place].num)
-				snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name, *port_cfg[0][place].num ? "yes" : "no");
-			else
-				snprintf(buf, bufsize, " -> %s:", port_spec[place].name);
-			break;
-		case MISDN_CTYPE_ASTGROUP:
-			if (port_cfg[port][place].grp)
-				snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name,
-						 ast_print_group(tempbuf, sizeof(tempbuf), *port_cfg[port][place].grp));
-			else if (port_cfg[0][place].grp)
-				snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name,
-						 ast_print_group(tempbuf, sizeof(tempbuf), *port_cfg[0][place].grp));
-			else
-				snprintf(buf, bufsize, " -> %s:", port_spec[place].name);
-			break;
-		case MISDN_CTYPE_ASTNAMEDGROUP:
-			if (port_cfg[port][place].namgrp) {
-				struct ast_str *tmp_str = ast_str_create(1024);
-				if (tmp_str) {
-					snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name,
-							ast_print_namedgroups(&tmp_str, port_cfg[port][place].namgrp));
-					ast_free(tmp_str);
-				}
-			} else if (port_cfg[0][place].namgrp) {
-				struct ast_str *tmp_str = ast_str_create(1024);
-				if (tmp_str) {
-					snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name,
-							ast_print_namedgroups(&tmp_str, port_cfg[0][place].namgrp));
-					ast_free(tmp_str);
-				}
-			} else {
-				snprintf(buf, bufsize, " -> %s:", port_spec[place].name);
-			}
-			break;
-		case MISDN_CTYPE_MSNLIST:
-			if (port_cfg[port][place].ml)
-				iter = port_cfg[port][place].ml;
-			else
-				iter = port_cfg[0][place].ml;
-			if (iter) {
-				for (; iter; iter = iter->next) {
-					strncat(tempbuf, iter->msn, sizeof(tempbuf) - strlen(tempbuf) - 1);
-				}
-				if (strlen(tempbuf) > 1) {
-					tempbuf[strlen(tempbuf)-2] = 0;
-				}
-			}
-			snprintf(buf, bufsize, " -> msns: %s", *tempbuf ? tempbuf : "none");
-			break;
-		case MISDN_CTYPE_STR:
-			if ( port_cfg[port][place].str) {
-				snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name, port_cfg[port][place].str);
-			} else if (port_cfg[0][place].str) {
-				snprintf(buf, bufsize, " -> %s: %s", port_spec[place].name, port_cfg[0][place].str);
-			} else {
-				snprintf(buf, bufsize, " -> %s:", port_spec[place].name);
-			}
-			break;
-		}
-	} else if (elem > MISDN_GEN_FIRST && elem < MISDN_GEN_LAST) {
-		switch (gen_spec[place].type) {
-		case MISDN_CTYPE_INT:
-		case MISDN_CTYPE_BOOLINT:
-			if (general_cfg[place].num)
-				snprintf(buf, bufsize, " -> %s: %d", gen_spec[place].name, *general_cfg[place].num);
-			else
-				snprintf(buf, bufsize, " -> %s:", gen_spec[place].name);
-			break;
-		case MISDN_CTYPE_BOOL:
-			if (general_cfg[place].num)
-				snprintf(buf, bufsize, " -> %s: %s", gen_spec[place].name, *general_cfg[place].num ? "yes" : "no");
-			else
-				snprintf(buf, bufsize, " -> %s:", gen_spec[place].name);
-			break;
-		case MISDN_CTYPE_STR:
-			if ( general_cfg[place].str) {
-				snprintf(buf, bufsize, " -> %s: %s", gen_spec[place].name, general_cfg[place].str);
-			} else {
-				snprintf(buf, bufsize, " -> %s:", gen_spec[place].name);
-			}
-			break;
-		default:
-			snprintf(buf, bufsize, " -> type of %s not handled yet", gen_spec[place].name);
-			break;
-		}
-	} else {
-		*buf = 0;
-		ast_log(LOG_WARNING, "Invalid call to misdn_cfg_get_config_string! Invalid config element (%d) requested.\n", elem);
-	}
-	misdn_cfg_unlock();
-}
-
-int misdn_cfg_get_next_port (int port)
-{
-	int p = -1;
-	int gn = map[MISDN_CFG_GROUPNAME];
-
-	misdn_cfg_lock();
-	for (port++; port <= max_ports; port++) {
-		if (port_cfg[port][gn].str) {
-			p = port;
-			break;
-		}
-	}
-	misdn_cfg_unlock();
-
-	return p;
-}
-
-int misdn_cfg_get_next_port_spin (int port)
-{
-	int p = misdn_cfg_get_next_port(port);
-	return (p > 0) ? p : misdn_cfg_get_next_port(0);
-}
-
-static int _parse (union misdn_cfg_pt *dest, const char *value, enum misdn_cfg_type type, int boolint_def)
-{
-	int re = 0;
-	int len, tmp;
-	char *valtmp;
-	char *tmp2 = ast_strdupa(value);
-
-	switch (type) {
-	case MISDN_CTYPE_STR:
-		if (dest->str) {
-			ast_free(dest->str);
-		}
-		if ((len = strlen(value))) {
-			dest->str = ast_malloc((len + 1) * sizeof(char));
-			strncpy(dest->str, value, len);
-			dest->str[len] = 0;
-		} else {
-			dest->str = ast_malloc(sizeof(char));
-			dest->str[0] = 0;
-		}
-		break;
-	case MISDN_CTYPE_INT:
-	{
-		int res;
-
-		if (strchr(value,'x')) {
-			res = sscanf(value, "%30x", &tmp);
-		} else {
-			res = sscanf(value, "%30d", &tmp);
-		}
-		if (res) {
-			if (!dest->num) {
-				dest->num = ast_malloc(sizeof(int));
-			}
-			memcpy(dest->num, &tmp, sizeof(int));
-		} else
-			re = -1;
-	}
-		break;
-	case MISDN_CTYPE_BOOL:
-		if (!dest->num) {
-			dest->num = ast_malloc(sizeof(int));
-		}
-		*(dest->num) = (ast_true(value) ? 1 : 0);
-		break;
-	case MISDN_CTYPE_BOOLINT:
-		if (!dest->num) {
-			dest->num = ast_malloc(sizeof(int));
-		}
-		if (sscanf(value, "%30d", &tmp)) {
-			memcpy(dest->num, &tmp, sizeof(int));
-		} else {
-			*(dest->num) = (ast_true(value) ? boolint_def : 0);
-		}
-		break;
-	case MISDN_CTYPE_MSNLIST:
-		for (valtmp = strsep(&tmp2, ","); valtmp; valtmp = strsep(&tmp2, ",")) {
-			if ((len = strlen(valtmp))) {
-				struct msn_list *ml = ast_malloc(sizeof(*ml));
-				ml->msn = ast_calloc(len+1, sizeof(char));
-				strncpy(ml->msn, valtmp, len);
-				ml->next = dest->ml;
-				dest->ml = ml;
-			}
-		}
-		break;
-	case MISDN_CTYPE_ASTGROUP:
-		if (!dest->grp) {
-			dest->grp = ast_malloc(sizeof(ast_group_t));
-		}
-		*(dest->grp) = ast_get_group(value);
-		break;
-	case MISDN_CTYPE_ASTNAMEDGROUP:
-		dest->namgrp = ast_get_namedgroups(value);
-		break;
-	}
-
-	return re;
-}
-
-static void _build_general_config (struct ast_variable *v)
-{
-	int pos;
-
-	for (; v; v = v->next) {
-		if (!ast_jb_read_conf(&global_jbconf, v->name, v->value))
-			continue;
-		if (((pos = get_cfg_position(v->name, GEN_CFG)) < 0) ||
-			(_parse(&general_cfg[pos], v->value, gen_spec[pos].type, gen_spec[pos].boolint_def) < 0))
-			CLI_ERROR(v->name, v->value, "general");
-	}
-}
-
-static void _build_port_config (struct ast_variable *v, char *cat)
-{
-	int pos, i;
-	union misdn_cfg_pt cfg_tmp[NUM_PORT_ELEMENTS];
-	int cfg_for_ports[max_ports + 1];
-
-	if (!v || !cat)
-		return;
-
-	memset(cfg_tmp, 0, sizeof(cfg_tmp));
-	memset(cfg_for_ports, 0, sizeof(cfg_for_ports));
-
-	if (!strcasecmp(cat, "default")) {
-		cfg_for_ports[0] = 1;
-	}
-
-	if (((pos = get_cfg_position("name", PORT_CFG)) < 0) ||
-		(_parse(&cfg_tmp[pos], cat, port_spec[pos].type, port_spec[pos].boolint_def) < 0)) {
-		CLI_ERROR(v->name, v->value, cat);
-		return;
-	}
-
-	for (; v; v = v->next) {
-		if (!strcasecmp(v->name, "ports")) {
-			char *token, *tmp = ast_strdupa(v->value);
-			char ptpbuf[BUFFERSIZE] = "";
-			int start, end;
-			for (token = strsep(&tmp, ","); token; token = strsep(&tmp, ","), *ptpbuf = 0) {
-				if (!*token)
-					continue;
-				if (sscanf(token, "%30d-%30d%511s", &start, &end, ptpbuf) >= 2) {
-					for (; start <= end; start++) {
-						if (start <= max_ports && start > 0) {
-							cfg_for_ports[start] = 1;
-							ptp[start] = (strstr(ptpbuf, "ptp")) ? 1 : 0;
-						} else
-							CLI_ERROR(v->name, v->value, cat);
-					}
-				} else {
-					if (sscanf(token, "%30d%511s", &start, ptpbuf)) {
-						if (start <= max_ports && start > 0) {
-							cfg_for_ports[start] = 1;
-							ptp[start] = (strstr(ptpbuf, "ptp")) ? 1 : 0;
-						} else
-							CLI_ERROR(v->name, v->value, cat);
-					} else
-						CLI_ERROR(v->name, v->value, cat);
-				}
-			}
-		} else {
-			if (((pos = get_cfg_position(v->name, PORT_CFG)) < 0) ||
-				(_parse(&cfg_tmp[pos], v->value, port_spec[pos].type, port_spec[pos].boolint_def) < 0))
-				CLI_ERROR(v->name, v->value, cat);
-		}
-	}
-
-	for (i = 0; i < (max_ports + 1); ++i) {
-		if (i > 0 && cfg_for_ports[0]) {
-			/* default category, will populate the port_cfg with additional port
-			categories in subsequent calls to this function */
-			memset(cfg_tmp, 0, sizeof(cfg_tmp));
-		}
-		if (cfg_for_ports[i]) {
-			memcpy(port_cfg[i], cfg_tmp, sizeof(cfg_tmp));
-		}
-	}
-}
-
-void misdn_cfg_update_ptp (void)
-{
-#ifndef MISDN_1_2
-	char misdn_init[BUFFERSIZE];
-	char line[BUFFERSIZE];
-	FILE *fp;
-	char *tok, *p, *end;
-	int port;
-
-	misdn_cfg_get(0, MISDN_GEN_MISDN_INIT, &misdn_init, sizeof(misdn_init));
-
-	if (!ast_strlen_zero(misdn_init)) {
-		fp = fopen(misdn_init, "r");
-		if (fp) {
-			while(fgets(line, sizeof(line), fp)) {
-				if (!strncmp(line, "nt_ptp", 6)) {
-					for (tok = strtok_r(line,",=", &p);
-						 tok;
-						 tok = strtok_r(NULL,",=", &p)) {
-						port = strtol(tok, &end, 10);
-						if (end != tok && misdn_cfg_is_port_valid(port)) {
-							misdn_cfg_lock();
-							ptp[port] = 1;
-							misdn_cfg_unlock();
-						}
-					}
-				}
-			}
-			fclose(fp);
-		} else {
-			ast_log(LOG_WARNING,"Couldn't open %s: %s\n", misdn_init, strerror(errno));
-		}
-	}
-#else
-	int i;
-	int proto;
-	char filename[128];
-	FILE *fp;
-
-	for (i = 1; i <= max_ports; ++i) {
-		snprintf(filename, sizeof(filename), "/sys/class/mISDN-stacks/st-%08x/protocol", i << 8);
-		fp = fopen(filename, "r");
-		if (!fp) {
-			ast_log(LOG_WARNING, "Could not open %s: %s\n", filename, strerror(errno));
-			continue;
-		}
-		if (fscanf(fp, "0x%08x", &proto) != 1)
-			ast_log(LOG_WARNING, "Could not parse contents of %s!\n", filename);
-		else
-			ptp[i] = proto & 1<<5 ? 1 : 0;
-		fclose(fp);
-	}
-#endif
-}
-
-static void _fill_defaults (void)
-{
-	int i;
-
-	for (i = 0; i < NUM_PORT_ELEMENTS; ++i) {
-		if (!port_cfg[0][i].any && strcasecmp(port_spec[i].def, NO_DEFAULT))
-			_parse(&(port_cfg[0][i]), (char *)port_spec[i].def, port_spec[i].type, port_spec[i].boolint_def);
-	}
-	for (i = 0; i < NUM_GEN_ELEMENTS; ++i) {
-		if (!general_cfg[i].any && strcasecmp(gen_spec[i].def, NO_DEFAULT))
-			_parse(&(general_cfg[i]), (char *)gen_spec[i].def, gen_spec[i].type, gen_spec[i].boolint_def);
-	}
-}
-
-void misdn_cfg_reload (void)
-{
-	misdn_cfg_init(0, 1);
-}
-
-void misdn_cfg_destroy (void)
-{
-	misdn_cfg_lock();
-
-	_free_port_cfg();
-	_free_general_cfg();
-
-	ast_free(port_cfg);
-	ast_free(general_cfg);
-	ast_free(ptp);
-	ast_free(map);
-
-	misdn_cfg_unlock();
-	ast_mutex_destroy(&config_mutex);
-}
-
-int misdn_cfg_init(int this_max_ports, int reload)
-{
-	char config[] = "misdn.conf";
-	char *cat, *p;
-	int i;
-	struct ast_config *cfg;
-	struct ast_variable *v;
-	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
-
-	if (!(cfg = ast_config_load2(config, "chan_misdn", config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
-		ast_log(LOG_WARNING, "missing or invalid file: misdn.conf\n");
-		return -1;
-	} else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
-		return 0;
-
-	ast_mutex_init(&config_mutex);
-
-	/* Copy the default jb config over global_jbconf */
-	memcpy(&global_jbconf, &default_jbconf, sizeof(struct ast_jb_conf));
-
-	misdn_cfg_lock();
-
-	if (this_max_ports) {
-		/* this is the first run */
-		max_ports = this_max_ports;
-		map = ast_calloc(MISDN_GEN_LAST + 1, sizeof(int));
-		if (_enum_array_map())
-			return -1;
-		p = ast_calloc(1, (max_ports + 1) * sizeof(union misdn_cfg_pt *)
-						   + (max_ports + 1) * NUM_PORT_ELEMENTS * sizeof(union misdn_cfg_pt));
-		port_cfg = (union misdn_cfg_pt **)p;
-		p += (max_ports + 1) * sizeof(union misdn_cfg_pt *);
-		for (i = 0; i <= max_ports; ++i) {
-			port_cfg[i] = (union misdn_cfg_pt *)p;
-			p += NUM_PORT_ELEMENTS * sizeof(union misdn_cfg_pt);
-		}
-		general_cfg = ast_calloc(1, sizeof(union misdn_cfg_pt *) * NUM_GEN_ELEMENTS);
-		ptp = ast_calloc(max_ports + 1, sizeof(int));
-	}
-	else {
-		/* misdn reload */
-		_free_port_cfg();
-		_free_general_cfg();
-		memset(port_cfg[0], 0, NUM_PORT_ELEMENTS * sizeof(union misdn_cfg_pt) * (max_ports + 1));
-		memset(general_cfg, 0, sizeof(union misdn_cfg_pt *) * NUM_GEN_ELEMENTS);
-		memset(ptp, 0, sizeof(int) * (max_ports + 1));
-	}
-
-	cat = ast_category_browse(cfg, NULL);
-
-	while(cat) {
-		v = ast_variable_browse(cfg, cat);
-		if (!strcasecmp(cat, "general")) {
-			_build_general_config(v);
-		} else {
-			_build_port_config(v, cat);
-		}
-		cat = ast_category_browse(cfg, cat);
-	}
-
-	_fill_defaults();
-
-	misdn_cfg_unlock();
-	ast_config_destroy(cfg);
-
-	return 0;
-}
-
-struct ast_jb_conf *misdn_get_global_jbconf() {
-	return &global_jbconf;
-}
diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c
index 176bc496a8c5162eddcf41ee857857947f5b0fd4..0496f048e3f93cddb221e0eae16a910d7f8193e3 100644
--- a/channels/pjsip/dialplan_functions.c
+++ b/channels/pjsip/dialplan_functions.c
@@ -69,6 +69,12 @@
 	</see-also>
 </function>
 <function name="PJSIP_DTMF_MODE" language="en_US">
+	<since>
+		<version>13.18.0</version>
+		<version>14.7.0</version>
+		<version>15.1.0</version>
+		<version>16.0.0</version>
+	</since>
 	<synopsis>
 		Get or change the DTMF mode for a SIP call.
 	</synopsis>
@@ -94,6 +100,11 @@
 	</description>
 </function>
 <function name="PJSIP_SEND_SESSION_REFRESH" language="en_US">
+	<since>
+		<version>13.12.0</version>
+		<version>14.1.0</version>
+		<version>15.0.0</version>
+	</since>
 	<synopsis>
 		W/O: Initiate a session refresh via an UPDATE or re-INVITE on an established media session
 	</synopsis>
@@ -135,6 +146,11 @@
 	</see-also>
 </function>
 <function name="PJSIP_PARSE_URI" language="en_US">
+	<since>
+		<version>13.24.0</version>
+		<version>16.1.0</version>
+		<version>17.0.0</version>
+	</since>
 	<synopsis>
 		Parse an uri and return a type part of the URI.
 	</synopsis>
@@ -288,6 +304,12 @@
 							<enum name="rtt">
 								<para>Round trip time</para>
 							</enum>
+							<enum name="txmes">
+								<para>Transmitted Media Experience Score</para>
+							</enum>
+							<enum name="rxmes">
+								<para>Received Media Experience Score</para>
+							</enum>
 						</enumlist>
 					</enum>
 					<enum name="all_jitter">
@@ -371,6 +393,37 @@
 							</enum>
 						</enumlist>
 					</enum>
+					<enum name="all_mes">
+						<para>Retrieve a summary of all RTCP Media Experience Score information.</para>
+						<para>The following data items are returned in a semi-colon
+						delineated list:</para>
+						<enumlist>
+							<enum name="minmes">
+								<para>Minimum MES based on us analysing received packets.</para>
+							</enum>
+							<enum name="maxmes">
+								<para>Maximum MES based on us analysing received packets.</para>
+							</enum>
+							<enum name="avgmes">
+								<para>Average MES based on us analysing received packets.</para>
+							</enum>
+							<enum name="stdevmes">
+								<para>Standard deviation MES based on us analysing received packets.</para>
+							</enum>
+							<enum name="reported_minmes">
+								<para>Minimum MES based on data we get in Sender and Receiver Reports sent by the remote end</para>
+							</enum>
+							<enum name="reported_maxmes">
+								<para>Maximum MES based on data we get in Sender and Receiver Reports sent by the remote end</para>
+							</enum>
+							<enum name="reported_avgmes">
+								<para>Average MES based on data we get in Sender and Receiver Reports sent by the remote end</para>
+							</enum>
+							<enum name="reported_stdevmes">
+								<para>Standard deviation MES based on data we get in Sender and Receiver Reports sent by the remote end</para>
+							</enum>
+						</enumlist>
+					</enum>
 					<enum name="txcount"><para>Transmitted packet count</para></enum>
 					<enum name="rxcount"><para>Received packet count</para></enum>
 					<enum name="txjitter"><para>Transmitted packet jitter</para></enum>
@@ -400,6 +453,24 @@
 					<enum name="stdevrtt"><para>Standard deviation round trip time</para></enum>
 					<enum name="local_ssrc"><para>Our Synchronization Source identifier</para></enum>
 					<enum name="remote_ssrc"><para>Their Synchronization Source identifier</para></enum>
+					<enum name="txmes"><para>
+					Current MES based on us analyzing rtt, jitter and loss
+					in the actual received RTP stream received from the remote end.
+					I.E.  This is the MES for the incoming audio stream.
+					</para></enum>
+					<enum name="rxmes"><para>
+					Current MES based on rtt and the jitter and loss values in
+					RTCP sender and receiver reports we receive from the
+					remote end. I.E.  This is the MES for the outgoing audio stream.
+					</para></enum>
+					<enum name="remote_maxmes"><para>Max MES based on data we get in Sender and Receiver Reports sent by the remote end</para></enum>
+					<enum name="remote_minmes"><para>Min MES based on data we get in Sender and Receiver Reports sent by the remote end</para></enum>
+					<enum name="remote_normdevmes"><para>Average MES based on data we get in Sender and Receiver Reports sent by the remote end</para></enum>
+					<enum name="remote_stdevmes"><para>Standard deviation MES based on data we get in Sender and Receiver Reports sent by the remote end</para></enum>
+					<enum name="local_maxmes"><para>Max MES based on us analyzing the received RTP stream</para></enum>
+					<enum name="local_minmes"><para>Min MES based on us analyzing the received RTP stream</para></enum>
+					<enum name="local_normdevmes"><para>Average MES based on us analyzing the received RTP stream</para></enum>
+					<enum name="local_stdevmes"><para>Standard deviation MES based on us analyzing the received RTP stream</para></enum>
 				</enumlist>
 			</parameter>
 			<parameter name="media_type" required="false">
@@ -662,6 +733,8 @@ static int channel_read_rtcp(struct ast_channel *chan, const char *type, const c
 			stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT;
 		} else if (!strcasecmp(type, "all_loss")) {
 			stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS;
+		} else if (!strcasecmp(type, "all_mes")) {
+			stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES;
 		}
 
 		if (!ast_rtp_instance_get_quality(media->rtp, stat_field, buf, buflen)) {
@@ -708,6 +781,16 @@ static int channel_read_rtcp(struct ast_channel *chan, const char *type, const c
 			{ "stdevrtt",              DBL, { .d8 = &stats.stdevrtt, }, },
 			{ "local_ssrc",            INT, { .i4 = &stats.local_ssrc, }, },
 			{ "remote_ssrc",           INT, { .i4 = &stats.remote_ssrc, }, },
+			{ "txmes",                 DBL, { .d8 = &stats.txmes, }, },
+			{ "rxmes",                 DBL, { .d8 = &stats.rxmes, }, },
+			{ "remote_maxmes",         DBL, { .d8 = &stats.remote_maxmes, }, },
+			{ "remote_minmes",         DBL, { .d8 = &stats.remote_minmes, }, },
+			{ "remote_normdevmes",     DBL, { .d8 = &stats.remote_normdevmes, }, },
+			{ "remote_stdevmes",       DBL, { .d8 = &stats.remote_stdevmes, }, },
+			{ "local_maxmes",          DBL, { .d8 = &stats.local_maxmes, }, },
+			{ "local_minmes",          DBL, { .d8 = &stats.local_minmes, }, },
+			{ "local_normdevmes",      DBL, { .d8 = &stats.local_normdevmes, }, },
+			{ "local_stdevmes",        DBL, { .d8 = &stats.local_stdevmes, }, },
 			{ NULL, },
 		};
 
diff --git a/channels/sig_analog.c b/channels/sig_analog.c
index 64f1114fde9bcd99a2e7e815b32bad1dd8bd1f77..5ae96d9418d2598acde993ae713ca23bebd88350 100644
--- a/channels/sig_analog.c
+++ b/channels/sig_analog.c
@@ -1033,6 +1033,10 @@ int analog_call(struct analog_pvt *p, struct ast_channel *ast, const char *rdest
 				ast_log(LOG_WARNING, "Number '%s' is shorter than stripmsd (%d)\n", c, p->stripmsd);
 				c = NULL;
 			}
+			if (c && (strlen(c) > sizeof(p->dop.dialstr) - 3 /* "Tw\0" */)) {
+				ast_log(LOG_WARNING, "Number '%s' is longer than %d bytes\n", c, (int)sizeof(p->dop.dialstr) - 2);
+				c = NULL;
+			}
 			if (c) {
 				p->dop.op = ANALOG_DIAL_OP_REPLACE;
 				snprintf(p->dop.dialstr, sizeof(p->dop.dialstr), "Tw%s", c);
@@ -1085,6 +1089,10 @@ int analog_call(struct analog_pvt *p, struct ast_channel *ast, const char *rdest
 		if (p->use_callerid) {
 			p->caller.id.name.str = p->lastcid_name;
 			p->caller.id.number.str = p->lastcid_num;
+			p->caller.id.name.valid = ast_channel_connected(ast)->id.name.valid;
+			p->caller.id.number.valid = ast_channel_connected(ast)->id.number.valid;
+			p->caller.id.name.presentation = ast_channel_connected(ast)->id.name.presentation;
+			p->caller.id.number.presentation = ast_channel_connected(ast)->id.number.presentation;
 		}
 
 		ast_setstate(ast, AST_STATE_RINGING);
@@ -2130,7 +2138,7 @@ static void *__analog_ss_thread(void *data)
 		/* If starting a threeway call, never timeout on the first digit so someone
 		   can use flash-hook as a "hold" feature */
 		if (p->subs[ANALOG_SUB_THREEWAY].owner) {
-			timeout = 999999;
+			timeout = INT_MAX;
 		}
 		while (len < AST_MAX_EXTENSION-1) {
 			int is_exten_parking = 0;
@@ -2231,12 +2239,12 @@ static void *__analog_ss_thread(void *data)
 			} else if (!strcmp(exten, pickupexten)) {
 				/* Scan all channels and see if there are any
 				 * ringing channels that have call groups
-				 * that equal this channels pickup group
+				 * that equal this channel's pickup group
 				 */
 				if (idx == ANALOG_SUB_REAL) {
 					/* Switch us from Third call to Call Wait */
 					if (p->subs[ANALOG_SUB_THREEWAY].owner) {
-						/* If you make a threeway call and the *8# a call, it should actually
+						/* If you make a threeway call and then *8# a call, it should actually
 						   look like a callwait */
 						analog_alloc_sub(p, ANALOG_SUB_CALLWAIT);
 						analog_swap_subs(p, ANALOG_SUB_CALLWAIT, ANALOG_SUB_THREEWAY);
@@ -2260,10 +2268,8 @@ static void *__analog_ss_thread(void *data)
 				ast_verb(3, "Disabling Caller*ID on %s\n", ast_channel_name(chan));
 				/* Disable Caller*ID if enabled */
 				p->hidecallerid = 1;
-				ast_party_number_free(&ast_channel_caller(chan)->id.number);
-				ast_party_number_init(&ast_channel_caller(chan)->id.number);
-				ast_party_name_free(&ast_channel_caller(chan)->id.name);
-				ast_party_name_init(&ast_channel_caller(chan)->id.name);
+				ast_channel_caller(chan)->id.number.presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED;
+				ast_channel_caller(chan)->id.name.presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED;
 				res = analog_play_tone(p, idx, ANALOG_TONE_DIALRECALL);
 				if (res) {
 					ast_log(LOG_WARNING, "Unable to do dial recall on channel %s: %s\n",
@@ -2349,7 +2355,8 @@ static void *__analog_ss_thread(void *data)
 				ast_verb(3, "Enabling Caller*ID on %s\n", ast_channel_name(chan));
 				/* Enable Caller*ID if enabled */
 				p->hidecallerid = 0;
-				ast_set_callerid(chan, p->cid_num, p->cid_name, NULL);
+				ast_channel_caller(chan)->id.number.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
+				ast_channel_caller(chan)->id.name.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
 				res = analog_play_tone(p, idx, ANALOG_TONE_DIALRECALL);
 				if (res) {
 					ast_log(LOG_WARNING, "Unable to do dial recall on channel %s: %s\n",
@@ -2768,9 +2775,13 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
 		analog_set_pulsedial(p, (res & ANALOG_EVENT_PULSEDIGIT) ? 1 : 0);
 		ast_debug(1, "Detected %sdigit '%c'\n", (res & ANALOG_EVENT_PULSEDIGIT) ? "pulse ": "", res & 0xff);
 		analog_confmute(p, 0);
-		p->subs[idx].f.frametype = AST_FRAME_DTMF_END;
-		p->subs[idx].f.subclass.integer = res & 0xff;
-		analog_handle_dtmf(p, ast, idx, &f);
+		if (p->dialmode == ANALOG_DIALMODE_BOTH || p->dialmode == ANALOG_DIALMODE_PULSE) {
+			p->subs[idx].f.frametype = AST_FRAME_DTMF_END;
+			p->subs[idx].f.subclass.integer = res & 0xff;
+			analog_handle_dtmf(p, ast, idx, &f);
+		} else {
+			ast_debug(1, "Dropping pulse digit '%c' because pulse dialing disabled on channel %d\n", res & 0xff, p->channel);
+		}
 		return f;
 	}
 
@@ -2804,7 +2815,7 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
 
 	switch (res) {
 	case ANALOG_EVENT_EC_DISABLED:
-		ast_verb(3, "Channel %d echo canceler disabled due to CED detection\n", p->channel);
+		ast_verb(3, "Channel %d echo canceller disabled due to CED detection\n", p->channel);
 		analog_set_echocanceller(p, 0);
 		break;
 #ifdef HAVE_DAHDI_ECHOCANCEL_FAX_MODE
@@ -2815,10 +2826,10 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
 		ast_verb(3, "Channel %d detected a CED tone from the network.\n", p->channel);
 		break;
 	case ANALOG_EVENT_EC_NLP_DISABLED:
-		ast_verb(3, "Channel %d echo canceler disabled its NLP.\n", p->channel);
+		ast_verb(3, "Channel %d echo canceller disabled its NLP.\n", p->channel);
 		break;
 	case ANALOG_EVENT_EC_NLP_ENABLED:
-		ast_verb(3, "Channel %d echo canceler enabled its NLP.\n", p->channel);
+		ast_verb(3, "Channel %d echo canceller enabled its NLP.\n", p->channel);
 		break;
 #endif
 	case ANALOG_EVENT_PULSE_START:
@@ -2903,14 +2914,14 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
 					analog_lock_sub_owner(p, ANALOG_SUB_CALLWAIT);
 					if (!p->subs[ANALOG_SUB_CALLWAIT].owner) {
 						/*
-						 * The call waiting call dissappeared.
+						 * The call waiting call disappeared.
 						 * This is now a normal hangup.
 						 */
 						analog_set_echocanceller(p, 0);
 						return NULL;
 					}
 
-					/* There's a call waiting call, so ring the phone, but make it unowned in the mean time */
+					/* There's a call waiting call, so ring the phone, but make it unowned in the meantime */
 					analog_swap_subs(p, ANALOG_SUB_CALLWAIT, ANALOG_SUB_REAL);
 					ast_verb(3, "Channel %d still has (callwait) call, ringing phone\n", p->channel);
 					analog_unalloc_sub(p, ANALOG_SUB_CALLWAIT);
@@ -3382,10 +3393,8 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
 						/* Put them in the threeway, and flip */
 						analog_set_inthreeway(p, ANALOG_SUB_THREEWAY, 1);
 						analog_set_inthreeway(p, ANALOG_SUB_REAL, 1);
-						if (ast_channel_state(ast) == AST_STATE_UP) {
-							analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL);
-							orig_3way_sub = ANALOG_SUB_REAL;
-						}
+						analog_swap_subs(p, ANALOG_SUB_THREEWAY, ANALOG_SUB_REAL);
+						orig_3way_sub = ANALOG_SUB_REAL;
 						ast_queue_unhold(p->subs[orig_3way_sub].owner);
 						analog_set_new_owner(p, p->subs[ANALOG_SUB_REAL].owner);
 					} else {
@@ -3726,6 +3735,32 @@ void *analog_handle_init_event(struct analog_pvt *i, int event)
 	/* Handle an event on a given channel for the monitor thread. */
 	switch (event) {
 	case ANALOG_EVENT_WINKFLASH:
+	case ANALOG_EVENT_RINGBEGIN:
+		switch (i->sig) {
+		case ANALOG_SIG_FXSLS:
+		case ANALOG_SIG_FXSGS:
+		case ANALOG_SIG_FXSKS:
+			if (i->immediate) {
+				if (i->use_callerid || i->usedistinctiveringdetection) {
+					ast_log(LOG_WARNING, "Can't start PBX immediately, must wait for Caller ID / distinctive ring\n");
+				} else {
+					/* If we don't care about Caller ID or Distinctive Ring, then there's
+					 * no need to wait for anything before accepting the call, as
+					 * waiting will buy us nothing.
+					 * So if the channel is configured for immediate, actually start immediately
+					 * and get the show on the road as soon as possible. */
+					ast_debug(1, "Disabling ring timeout (previously %d) to begin handling immediately\n", i->ringt_base);
+					analog_set_ringtimeout(i, 0);
+				}
+			}
+			break;
+		default:
+			break;
+		}
+		/* Fall through */
+		if (!(ISTRUNK(i) && i->immediate && !i->use_callerid && !i->usedistinctiveringdetection)) {
+			break;
+		}
 	case ANALOG_EVENT_RINGOFFHOOK:
 		if (i->inalarm) {
 			break;
diff --git a/channels/sig_analog.h b/channels/sig_analog.h
index 488be3662e9ce275c683aba2a278112fb1853ad4..07e1cdd2aa1a7613c05074c8e495993b79891491 100644
--- a/channels/sig_analog.h
+++ b/channels/sig_analog.h
@@ -116,6 +116,13 @@ enum analog_dsp_digitmode {
 	ANALOG_DIGITMODE_MF,
 };
 
+enum analog_dialmode {
+	ANALOG_DIALMODE_BOTH = 0,
+	ANALOG_DIALMODE_PULSE,
+	ANALOG_DIALMODE_DTMF,
+	ANALOG_DIALMODE_NONE,
+};
+
 enum analog_cid_start {
 	ANALOG_CID_START_POLARITY = 1,
 	ANALOG_CID_START_POLARITY_IN,
@@ -266,7 +273,7 @@ struct analog_pvt {
 	enum analog_sigtype sig;
 	/* To contain the private structure passed into the channel callbacks */
 	void *chan_pvt;
-	/* All members after this are giong to be transient, and most will probably change */
+	/* All members after this are going to be transient, and most will probably change */
 	struct ast_channel *owner;			/*!< Our current active owner (if applicable) */
 
 	struct analog_subchannel subs[3];		/*!< Sub-channels */
@@ -308,6 +315,7 @@ struct analog_pvt {
 	int channel;					/*!< Channel Number */
 
 	enum analog_sigtype outsigmod;
+	enum analog_dialmode dialmode;	/*!< Which of pulse and/or tone dialing to support */
 	int echotraining;
 	int cid_signalling;				/*!< Asterisk callerid type we're using */
 	int polarityonanswerdelay;
diff --git a/codecs/codec_ulaw.c b/codecs/codec_ulaw.c
index 609d06d60dfc2e3dbc53e093a822dcaaee6599c1..00a0916ae502c186585cc529613892a55c8930fa 100644
--- a/codecs/codec_ulaw.c
+++ b/codecs/codec_ulaw.c
@@ -97,25 +97,6 @@ static struct ast_translator ulawtolin = {
 	.buf_size = BUFFER_SAMPLES * 2,
 };
 
-static struct ast_translator testlawtolin = {
-	.name = "testlawtolin",
-	.src_codec = {
-		.name = "testlaw",
-		.type = AST_MEDIA_TYPE_AUDIO,
-		.sample_rate = 8000,
-	},
-	.dst_codec = {
-		.name = "slin",
-		.type = AST_MEDIA_TYPE_AUDIO,
-		.sample_rate = 8000,
-	},
-	.format = "slin",
-	.framein = ulawtolin_framein,
-	.sample = ulaw_sample,
-	.buffer_samples = BUFFER_SAMPLES,
-	.buf_size = BUFFER_SAMPLES * 2,
-};
-
 /*!
  * \brief The complete translator for LinToulaw.
  */
@@ -139,33 +120,12 @@ static struct ast_translator lintoulaw = {
 	.buffer_samples = BUFFER_SAMPLES,
 };
 
-static struct ast_translator lintotestlaw = {
-	.name = "lintotestlaw",
-	.src_codec = {
-		.name = "slin",
-		.type = AST_MEDIA_TYPE_AUDIO,
-		.sample_rate = 8000,
-	},
-	.dst_codec = {
-		.name = "testlaw",
-		.type = AST_MEDIA_TYPE_AUDIO,
-		.sample_rate = 8000,
-	},
-	.format = "testlaw",
-	.framein = lintoulaw_framein,
-	.sample = slin8_sample,
-	.buf_size = BUFFER_SAMPLES,
-	.buffer_samples = BUFFER_SAMPLES,
-};
-
 static int unload_module(void)
 {
 	int res;
 
 	res = ast_unregister_translator(&lintoulaw);
 	res |= ast_unregister_translator(&ulawtolin);
-	res |= ast_unregister_translator(&testlawtolin);
-	res |= ast_unregister_translator(&lintotestlaw);
 
 	return res;
 }
@@ -176,8 +136,6 @@ static int load_module(void)
 
 	res = ast_register_translator(&ulawtolin);
 	res |= ast_register_translator(&lintoulaw);
-	res |= ast_register_translator(&lintotestlaw);
-	res |= ast_register_translator(&testlawtolin);
 
 	if (res) {
 		unload_module();
diff --git a/codecs/codecs.xml b/codecs/codecs.xml
index 996a3559b758de4e1f09db99d6b74687fda14c5b..c7833e6c81da2e5656d64e3561a2ff10fffb9fad 100644
--- a/codecs/codecs.xml
+++ b/codecs/codecs.xml
@@ -1,4 +1,4 @@
-<member name="codec_opus" displayname="Download the Opus codec from Digium.  See http://downloads.digium.com/pub/telephony/codec_opus/README.">
+<member name="codec_opus" displayname="Download the Opus codec from Digium.  See https://downloads.digium.com/pub/telephony/codec_opus/README.">
 	<support_level>external</support_level>
 	<conflict>no_binary_modules</conflict>
 	<depend>xmlstarlet</depend>
@@ -6,28 +6,28 @@
 	<depend>res_format_attr_opus</depend>
 	<defaultenabled>no</defaultenabled>
 </member>
-<member name="codec_silk" displayname="Download the SILK codec from Digium.  See http://downloads.digium.com/pub/telephony/codec_silk/README.">
+<member name="codec_silk" displayname="Download the SILK codec from Digium.  See https://downloads.digium.com/pub/telephony/codec_silk/README.">
 	<support_level>external</support_level>
 	<conflict>no_binary_modules</conflict>
 	<depend>xmlstarlet</depend>
 	<depend>bash</depend>
 	<defaultenabled>no</defaultenabled>
 </member>
-<member name="codec_siren7" displayname="Download the Siren7 codec from Digium.  See http://downloads.digium.com/pub/telephony/codec_siren7/README.">
+<member name="codec_siren7" displayname="Download the Siren7 codec from Digium.  See https://downloads.digium.com/pub/telephony/codec_siren7/README.">
 	<support_level>external</support_level>
 	<conflict>no_binary_modules</conflict>
 	<depend>xmlstarlet</depend>
 	<depend>bash</depend>
 	<defaultenabled>no</defaultenabled>
 </member>
-<member name="codec_siren14" displayname="Download the Siren14 codec from Digium.  See http://downloads.digium.com/pub/telephony/codec_siren14/README.">
+<member name="codec_siren14" displayname="Download the Siren14 codec from Digium.  See https://downloads.digium.com/pub/telephony/codec_siren14/README.">
 	<support_level>external</support_level>
 	<conflict>no_binary_modules</conflict>
 	<depend>xmlstarlet</depend>
 	<depend>bash</depend>
 	<defaultenabled>no</defaultenabled>
 </member>
-<member name="codec_g729a" displayname="Download the g729a codec from Digium.  A license must be purchased for this codec.  See http://downloads.digium.com/pub/telephony/codec_g729/README.">
+<member name="codec_g729a" displayname="Download the g729a codec from Digium.  A license must be purchased for this codec.  See https://downloads.digium.com/pub/telephony/codec_g729/README.">
 	<support_level>external</support_level>
 	<conflict>no_binary_modules</conflict>
 	<depend>xmlstarlet</depend>
diff --git a/configs/samples/aeap.conf.sample b/configs/samples/aeap.conf.sample
new file mode 100644
index 0000000000000000000000000000000000000000..2431ea3259e190dd0f005cac2fa91917aa489965
--- /dev/null
+++ b/configs/samples/aeap.conf.sample
@@ -0,0 +1,21 @@
+;
+; This file is used by the res_aeap module to configure parameters
+; used for AEAP applications.
+;
+;[myclient]
+;
+; type must be "client".
+;type=client
+;
+; URL used to connect to a server. It must be a websocket URL (ws or wss).
+;url=ws://127.0.0.1:9099
+;
+; codecs is comma separated string of allowed/disallowed codec names.
+;codecs=!all,ulaw,alaw,opus
+;
+; protocol is the implementation specific sub-protocol
+;protocol=speech_to_text
+;
+; "@" parameters can be specified and are used to to set custom values to
+; be passed as "params" in the initial "setup" request.
+;@language=en-US
diff --git a/configs/samples/amd.conf.sample b/configs/samples/amd.conf.sample
index d1764b50ceab7474ea64008f99e647ca8531bd96..b10829833139e8d1abb4a8932c8b1330ab91c2bc 100644
--- a/configs/samples/amd.conf.sample
+++ b/configs/samples/amd.conf.sample
@@ -8,6 +8,11 @@ total_analysis_time = 5000	; Maximum time allowed for the algorithm to decide
 silence_threshold = 256		; If the average level of noise in a sample does not reach
 				; this value, from a scale of 0 to 32767, then we will consider
 				; it to be silence.
+;playback_file = 		; Audio file to play while AMD is running, so the caller
+				; does not just hear silence. Note that specifying this here
+				; will apply to ALL AMD runs, so you may wish to set it
+				; in the dialplan as an argument to AMD() instead.
+				; Default is no audio file (not to play anything).
 
 ; Greeting ;
 initial_silence = 2500		; Maximum silence duration before the greeting.
@@ -19,7 +24,7 @@ greeting = 1500			; Maximum length of a greeting. If exceeded, then the
 
 ; Word detection ;
 min_word_length = 100		; Minimum duration of Voice to considered as a word
-maximum_word_length = 5000  	; Maximum duration of a single Voice utterance allowed.
+maximum_word_length = 5000	; Maximum duration of a single Voice utterance allowed.
 between_words_silence = 50	; Minimum duration of silence after a word to consider
 				; the audio what follows as a new word
 
diff --git a/configs/samples/app_mysql.conf.sample b/configs/samples/app_mysql.conf.sample
deleted file mode 100644
index 63ddc406e85911ba0291d1b0899b7dcc8c695d5d..0000000000000000000000000000000000000000
--- a/configs/samples/app_mysql.conf.sample
+++ /dev/null
@@ -1,24 +0,0 @@
-; Configuration file for the MYSQL app addon
-
-[general]
-;
-; Nullvalue governs how NULL values are returned from the database.  In
-; previous versions, the special NULL value was returned as the "NULL"
-; string.  We now provide an option for the behavior, configured globally.
-; nullstring  - the string "NULL"
-; emptystring - the string ""
-; null        - unset the variable
-;
-; WARNING: setting nullvalue=null may have undesirable consequences, in
-; particular if you use subroutines in AEL or the LOCAL() variable construct.
-; You have been warned.  Don't complain if you use that setting in combination
-; with Gosub or AEL and get buggy behavior.
-;
-nullvalue = nullstring
-
-; If set, autoclear will destroy allocated statement and connection resources
-; when the channel ends.  For most usage of the MYSQL app, this is what you
-; want, but it's conceivable that somebody is sharing MYSQL connections across
-; multiple channels, in which case, this should be set to 'no'.  Defaults to
-; 'no', as this was the original behavior.
-autoclear=yes
diff --git a/configs/samples/asterisk.conf.sample b/configs/samples/asterisk.conf.sample
index efb33862aea4fa343fb5d8dc0092d91f7d0513e4..0d0d2a05501ec79ffd5f186c8bf85dba2203e304 100644
--- a/configs/samples/asterisk.conf.sample
+++ b/configs/samples/asterisk.conf.sample
@@ -1,5 +1,5 @@
 [directories](!)
-astcachedir => /tmp
+astcachedir => /var/cache/asterisk
 astetcdir => /etc/asterisk
 astmoddir => /usr/lib/asterisk/modules
 astvarlibdir => /var/lib/asterisk
@@ -95,10 +95,13 @@ documentation_language = en_US	; Set the language you want documentation
 				;         documented in extensions.conf.sample.
 				; Default gosub.
 ;live_dangerously = no		; Enable the execution of 'dangerous' dialplan
-				; functions from external sources (AMI,
-				; etc.) These functions (such as SHELL) are
-				; considered dangerous because they can allow
-				; privilege escalation.
+				; functions and configuration file access from
+				; external sources (AMI, etc.) These functions
+				; (such as SHELL) are considered dangerous
+				; because they can allow privilege escalation.
+				; Configuration files are considered dangerous
+				; if they exist outside of the Asterisk
+				; configuration directory.
 				; Default no
 ;entityid=00:11:22:33:44:55	; Entity ID.
 				; This is in the form of a MAC address.
diff --git a/configs/samples/cdr.conf.sample b/configs/samples/cdr.conf.sample
index 4fce18b260dc5e5b39fcebfdeedc49c78adaca97..46ddb96b7d7ea942f8ee03a0ccc778c73579165f 100644
--- a/configs/samples/cdr.conf.sample
+++ b/configs/samples/cdr.conf.sample
@@ -32,6 +32,17 @@
 ; is "no".
 ;congestion = no
 
+; Define whether or not to ignore bridging changes in CDRs. This prevents
+; bridging changes from resulting in multiple CDRs for different parts of
+; a call. Default is "no". This setting cannot be changed on a reload.
+;ignorestatechanges = no
+
+; Define whether or not to ignore dial updates in CDRs. This prevents
+; dial updates from resulting in multiple CDRs for different parts of
+; a call. The last disposition on the channel will be used for the CDR.
+; Use with caution. Default is "no".
+;ignoredialchanges = no
+
 ; Normally, CDR's are not closed out until after all extensions are finished
 ; executing.  By enabling this option, the CDR will be ended before executing
 ; the "h" extension and hangup handlers so that CDR values such as "end" and
@@ -102,8 +113,6 @@
 ; Here are all the possible back ends:
 ;
 ;   csv, custom, manager, odbc, pgsql, radius, sqlite, tds
-;    (also, mysql is available via the asterisk-addons, due to licensing
-;     requirements)
 ;   (please note, also, that other backends can be created, by creating
 ;    a new backend module in the source cdr/ directory!)
 ;
diff --git a/configs/samples/cdr_mysql.conf.sample b/configs/samples/cdr_mysql.conf.sample
deleted file mode 100644
index a1f7d38e8cbd5dc4be8e669f98e6c16fc5ea153e..0000000000000000000000000000000000000000
--- a/configs/samples/cdr_mysql.conf.sample
+++ /dev/null
@@ -1,62 +0,0 @@
-;
-; Note - if the database server is hosted on the same machine as the
-; asterisk server, you can achieve a local Unix socket connection by
-; setting hostname=localhost
-;
-; port and sock are both optional parameters.  If hostname is specified
-; and is not "localhost" (you can use address 127.0.0.1 instead), then
-; cdr_mysql will attempt to connect to the port specified or use the
-; default port.  If hostname is not specified or if hostname is
-; "localhost", then cdr_mysql will attempt to connect to the socket file
-; specified by sock or otherwise use the default socket file.
-;
-;[global]
-;hostname=database.host.name
-;dbname=asteriskcdrdb
-;table=cdr
-;password=password
-;user=asteriskcdruser
-;port=3306
-;sock=/tmp/mysql.sock
-; By default CDRs are logged in the system's time zone
-;cdrzone=UTC               ; log CDRs with UTC
-;usegmtime=yes ;log date/time in GMT.  Default is "no"
-;cdrzone=America/New_York  ; or use a specific time zone
-;
-; If your system's locale differs from mysql database character set,
-; cdr_mysql can damage non-latin characters in CDR variables. Use this
-; option to protect your data.
-;charset=koi8r
-;
-; Older versions of cdr_mysql set the calldate field to whenever the
-; record was posted, rather than the start date of the call.  This flag
-; reverts to the old (incorrect) behavior.  Note that you'll also need
-; to comment out the "start=calldate" alias, below, to use this.
-;compat=no
-;
-; ssl connections (optional)
-;ssl_ca=<path to CA cert>
-;ssl_cert=<path to cert>
-;ssl_key=<path to keyfile>
-;
-; You may also configure the field names used in the CDR table.
-;
-[columns]
-;static "<value>" => <column>
-;alias <cdrvar> => <column>
-alias start => calldate
-;alias clid => <a_field_not_named_clid>
-;alias src => <a_field_not_named_src>
-;alias dst => <a_field_not_named_dst>
-;alias dcontext => <a_field_not_named_dcontext>
-;alias channel => <a_field_not_named_channel>
-;alias dstchannel => <a_field_not_named_dstchannel>
-;alias lastapp => <a_field_not_named_lastapp>
-;alias lastdata => <a_field_not_named_lastdata>
-;alias duration => <a_field_not_named_duration>
-;alias billsec => <a_field_not_named_billsec>
-;alias disposition => <a_field_not_named_disposition>
-;alias amaflags => <a_field_not_named_amaflags>
-;alias accountcode => <a_field_not_named_accountcode>
-;alias userfield => <a_field_not_named_userfield>
-;alias uniqueid => <a_field_not_named_uniqueid>
diff --git a/configs/samples/cdr_syslog.conf.sample b/configs/samples/cdr_syslog.conf.sample
deleted file mode 100644
index 3a619be9f8eca1a4a61ecb76d948f62a6a6c4d40..0000000000000000000000000000000000000000
--- a/configs/samples/cdr_syslog.conf.sample
+++ /dev/null
@@ -1,83 +0,0 @@
-;
-; Asterisk Call Detail Records (CDR) - Syslog Backend
-;
-
-; The cdr_syslog module writes CDRs using the facilities provided by syslog.
-;
-; Not only must you configure cdr_syslog from this file (cdr_syslog.conf) but
-; you will also need to make changes to your /etc/syslog.conf before CDRs will
-; be written to syslog.
-;
-; As an example, you can add the following to /etc/syslog.conf:
-;
-;    local4.info        /var/log/asterisk-cdr.log
-;
-; And then instruct syslogd to re-read the configuration file by sending it a
-; HUP signal.  On Linux this can be done like this:
-;
-;    kill -HUP `cat /var/run/syslogd.pid`
-;
-; Finally, you will need to uncomment the [cdr-simple] section below, and restart
-; Asterisk.  When calls are placed, you should start seeing records appear in
-; /var/log/asterisk-cdr.log.
-
-[general]
-; Facility
-;
-; The 'facility' keyword specifies the syslog facility to use when writing out
-; CDRs.
-;
-; Accepted values: One of the following:
-;                  user, local0, local1, local2, local3, local4, local5, local6
-;                  and local7.
-;
-;                  Note: Depending on your platform, the following may also be
-;                        available:
-;                        auth, authpriv, cron, daemon, ftp, kern, lpr, mail,
-;                        news, syslog, and uucp.
-;
-; Default value:   local4
-
-;facility=local0
-
-; Priority
-;
-; Use the 'priority' keyword to select which of the syslog priority levels to
-; use when logging CDRs.
-;
-; Accepted values: One of the following:
-;                  alert, crit, debug, emerg, err, info, notice, warning
-; Default value:   info
-
-;priority=warn
-
-; Note: The settings for 'facility' and 'priority' in the [general] section
-;       define the default values for all of the logging locations created
-;       below in separate sections.
-
-;[cdr-master]
-;facility = local5
-;priority = debug
-
-; Template
-;
-; The 'template' value allows you to specify a custom format for messages
-; written to syslog.  This is similar to how cdr_custom is configured.
-;
-; Allowed values: A diaplan style string.
-; Default value:  None, this is required field.
-;
-; Note: Because of the way substitution is done, the only meaningful values
-;       available when the record is logged are those available via the CDR()
-;       dialplan function.  All other channel variables will be unavailable.
-
-;template = "${CDR(clid)}","${CDR(src)}","${CDR(dst)}","${CDR(dcontext)}","${CDR(channel)}","${CDR(dstchannel)}","${CDR(lastapp)}","${CDR(lastdata)}","${CDR(start)}","${CDR(answer)}","${CDR(end)}","${CDR(duration)}","${CDR(billsec)}","${CDR(disposition)}","${CDR(amaflags)}","${CDR(accountcode)}","${CDR(uniqueid)}","${CDR(userfield)}"
-
-; High Resolution Time for billsec and duration fields
-;template = "${CDR(clid)}","${CDR(src)}","${CDR(dst)}","${CDR(dcontext)}","${CDR(channel)}","${CDR(dstchannel)}","${CDR(lastapp)}","${CDR(lastdata)}","${CDR(start)}","${CDR(answer)}","${CDR(end)}","${CDR(duration,f)}","${CDR(billsec,f)}","${CDR(disposition)}","${CDR(amaflags)}","${CDR(accountcode)}","${CDR(uniqueid)}","${CDR(userfield)}"
-;[cdr-simple]
-
-; Since we don't specify a facility or priority for this logging location, the
-; records will use the defaults specified in the [general] section.
-
-;template = "We received a call from ${CDR(src)}"
diff --git a/configs/samples/chan_dahdi.conf.sample b/configs/samples/chan_dahdi.conf.sample
index 5c3af43623e6bc57c35574abf482c3aab4e5c509..c54f482566715b4560e9e013bec08774ab1f75bc 100644
--- a/configs/samples/chan_dahdi.conf.sample
+++ b/configs/samples/chan_dahdi.conf.sample
@@ -936,8 +936,13 @@ pickupgroup=1
                                                 ; target of the transfer.
 
 ;
-; Specify whether the channel should be answered immediately or if the simple
-; switch should provide dialtone, read digits, etc.
+; On FXS channels (FXO signaled), specifies whether the channel should enter the dialplan
+; immediately or if the simple switch should provide dialtone, read digits, etc.
+; On FXO channels (FXS signaled), specifies whether the call should enter the dialplan
+; immediately or if we should wait for at least one ring. This is required if
+; Caller ID or distinctive ringing is enabled. If you do not need either, you can
+; skip waiting for the first ring to begin call processing sooner.
+;
 ; Note: If immediate=yes the dialplan execution will always start at extension
 ; 's' priority 1 regardless of the dialed number!
 ;
@@ -1126,10 +1131,19 @@ pickupgroup=1
 ;
 ; For FXO (FXS signalled) devices, whether to use pulse dial instead of DTMF
 ; Pulse digits from phones (FXS devices, FXO signalling) are always
-; detected.
+; detected, unless the dialmode setting has been changed from the default.
 ;
 ;pulsedial=yes
 ;
+; For FXS (FXO signalled) devices, the dialing modes to support for the channel.
+; By default, both pulse and tone (DTMF) dialing are always detected.
+; May be set to "pulse" if you only want to allow pulse dialing on a line.
+; May be set to "dtmf" or "tone" to only allow tone dialing on a line.
+; May be set to "none" to prevent dialing entirely.
+; You can also change this during a call using the CHANNEL function in the dialplan.
+;
+;dialmode=both
+;
 ; For fax detection, uncomment one of the following lines.  The default is *OFF*
 ;
 ;faxdetect=both
@@ -1286,10 +1300,11 @@ pickupgroup=1
 ; You can define your own custom ring cadences here.  You can define up to 8
 ; pairs.  If the silence is negative, it indicates where the caller ID spill is
 ; to be placed.  Also, if you define any custom cadences, the default cadences
-; will be turned off.
+; will be turned off (overwritten).
 ;
 ; This setting is global, rather than per-channel. It will not update on
-; a reload.
+; a reload, but new and modified cadences will update on dahdi restart.
+; A maximum of 24 cadences may be specified.
 ;
 ; Syntax is:  cadence=ring,silence[,ring,silence[...]]
 ;
diff --git a/configs/samples/confbridge.conf.sample b/configs/samples/confbridge.conf.sample
index eecbb65d64eca09ba73601993daec60ee0563824..f582323f10c1a067d37be5dc29797ba435ff5969 100644
--- a/configs/samples/confbridge.conf.sample
+++ b/configs/samples/confbridge.conf.sample
@@ -41,6 +41,12 @@ type=user
                ; There are some prompts, such as the prompt to enter a PIN number,
                ; that must be played regardless of what this option is set to.
                ; Off by default
+;hear_own_join_sound=yes  ; Sets if a user joining the conference should hear the sound_join
+                          ; audio sound when they enter the conference. If set to 'no' the
+                          ; user will not hear the sound_join audio but the other participants
+                          ; in the conference will still hear the audio. If set to 'yes'
+                          ; everyone hears the sound_join audio when this user enters the conference.
+                          ; On by default
 ;announce_user_count=yes  ; Sets if the number of users should be announced to the
                           ; caller.  Off by default.
 ;announce_user_count_all=yes ; Sets if the number of users should be announced to
@@ -52,8 +58,11 @@ type=user
                           ; when a channel enters a empty conference.  On by default.
 ;wait_marked=yes   ; Sets if the user must wait for a marked user to enter before
                    ; joining the conference. Off by default.
-;end_marked=yes ; This option will kick every user with this option set in their
-                ; user profile after the last Marked user exists the conference.
+;end_marked=yes ; This option will kick every non-marked user with this option set in their
+                ; user profile after the last marked user exits the conference.
+;end_marked_any=no ; This option will kick every user with this option set in
+                   ; their user profile after any marked user exits the conference.
+                   ; Additionally, note that unlike end_marked, this includes marked users.
 
 ;dsp_drop_silence=yes  ; This option drops what Asterisk detects as silence from
                        ; entering into the bridge.  Enabling this option will drastically
diff --git a/configs/samples/dundi.conf.sample b/configs/samples/dundi.conf.sample
index 3e8da2d1f510788ff3578db2b7564889b555e394..2f28f453bba7ca901b5d4aa131518406fba222aa 100644
--- a/configs/samples/dundi.conf.sample
+++ b/configs/samples/dundi.conf.sample
@@ -75,6 +75,18 @@ autokill=yes
 ; off by default due to performance impacts.
 ;
 ;storehistory=yes
+;
+; Channel technology to use for outgoing calls using SIP (Session Initiation Protocol).
+; Options are 'SIP' for chan_sip and 'PJSIP' for chan_pjsip. Default is 'PJSIP'.
+; If specified, all outgoing SIP calls using DUNDi will use the specified channel tech.
+;
+;outgoing_sip_tech=pjsip
+;
+; Name of endpoint from pjsip.conf to use for outgoing calls from this system,
+; when using the PJSIP technology to complete a call to a SIP-based destination.
+; (Required for PJSIP, since PJSIP calls must specify an endpoint explicitly).
+;
+;pjsip_outgoing_endpoint=outgoing
 
 [mappings]
 ;
diff --git a/configs/samples/extconfig.conf.sample b/configs/samples/extconfig.conf.sample
index b633fafa61feb512c9740f29af67c6894771532d..0e53823ddf05d344024fa5377e5682c227d54197 100644
--- a/configs/samples/extconfig.conf.sample
+++ b/configs/samples/extconfig.conf.sample
@@ -64,16 +64,15 @@
 ; "odbc" is shown in the examples below, but is not the only valid realtime
 ; engine.  Here are several of the possible options:
 ;    odbc ... res_config_odbc
-;    sqlite ... res_config_sqlite
 ;    sqlite3 ... res_config_sqlite3
 ;    pgsql ... res_config_pgsql
 ;    curl ... res_config_curl
 ;    ldap ... res_config_ldap
 ;    mysql ... res_config_mysql (available via add-ons in menuselect)
 ;
-; Note: The res_config_pgsql and res_config_sqlite backends configure the
-; database used in their respective configuration files and ignore the
-; database name configured in this file.
+; Note: The res_config_pgsql backend configures the database used in their
+; respective configuration files and ignore the database name configured in
+; this file.
 ;
 ;iaxusers => odbc,asterisk
 ;iaxpeers => odbc,asterisk
diff --git a/configs/samples/extensions.conf.sample b/configs/samples/extensions.conf.sample
index cd26dc368a62293e7d4372fa55f11511fd886260..d1cc4bd2b64981f61f30193addbb6a91a499925e 100644
--- a/configs/samples/extensions.conf.sample
+++ b/configs/samples/extensions.conf.sample
@@ -61,8 +61,9 @@ writeprotect=no
 ; this value to "yes" !!
 ; Please, if you try this out, and are forced to return to the
 ; old pattern matcher, please report your reasons in a bug report
-; on https://issues.asterisk.org. We have made good progress in providing
-; something compatible with the old matcher; help us finish the job!
+; on https://github.com/asterisk/asterisk/issues. We have made good
+; progress in providing something compatible with the old matcher; help us
+; finish the job!
 ;
 ; This value can be switched at runtime using the cli command "dialplan set extenpatternmatchnew true"
 ; or "dialplan set extenpatternmatchnew false", so you can experiment to your hearts content.
diff --git a/configs/samples/features.conf.sample b/configs/samples/features.conf.sample
index afdcf5ae4fe261123089f4fb12ffe8aa6b91118b..05b514e7b98d481ee95ac8857b4c294bcc900932 100644
--- a/configs/samples/features.conf.sample
+++ b/configs/samples/features.conf.sample
@@ -6,7 +6,11 @@
 
 [general]
 ;transferdigittimeout => 3      ; Number of seconds to wait between digits when transferring a call
-                                ; (default is 3 seconds)
+                                ; (default is 3 seconds). If the TRANSFER_EXTEN dialplan variable has been set
+                                ; on the channel of the user that is invoking the transfer feature, then
+                                ; this option is not used as the user is transferred directly to the extension
+                                ; specified by TRANSFER_EXTEN (the transfer context remains the context specified
+                                ; by TRANSFER_CONTEXT, if set, and otherwise the default context).
 ;xfersound = beep               ; to indicate an attended transfer is complete
 ;xferfailsound = beeperr        ; to indicate a failed transfer
 ;pickupexten = *8               ; Configure the pickup extension. (default is *8)
@@ -26,12 +30,13 @@
                                 ; By default, this is 2.
 ;transferdialattempts = 3       ; Number of times that a transferer may attempt to dial an extension before
                                 ; being kicked back to the original call.
+;transferannouncesound = beep   ; Sound to play to a transferer to indicate transfer process has begun. If empty, no sound will be played.
 ;transferretrysound = beep      ; Sound to play when a transferer fails to dial a valid extension.
 ;transferinvalidsound = beeperr ; Sound to play when a transferer fails to dial a valid extension and is out of retries.
 ;atxferabort = *1               ; cancel the attended transfer
 ;atxfercomplete = *2            ; complete the attended transfer, dropping out of the call
 ;atxferthreeway = *3            ; complete the attended transfer, but stay in the call. This will turn the call into a multi-party bridge
-;atxferswap = *4                ; swap to the other party. Once an attended transfer has begun, this options may be used multiple times
+;atxferswap = *4                ; swap to the other party. Once an attended transfer has begun, this option may be used multiple times
 
 ; Note that the DTMF features listed below only work when two channels have answered and are bridged together.
 ; They can not be used while the remote party is ringing or in progress. If you require this feature you can use
diff --git a/configs/samples/geolocation.conf.sample b/configs/samples/geolocation.conf.sample
new file mode 100644
index 0000000000000000000000000000000000000000..fdb9614b9c4992709a8c4342eec92049ca95eeb5
--- /dev/null
+++ b/configs/samples/geolocation.conf.sample
@@ -0,0 +1,318 @@
+;--
+  Geolocation Profile Sample Configuration
+
+  Please see https://wiki.asterisk.org/wiki/display/AST/Geolocation
+  for the most current information.
+--;
+
+;--
+=======================================================================
+  Overview
+=======================================================================
+
+Geolocation information is actually comprised of two objects, a
+Location object, and a Profile object.
+
+Location objects must contain one of the following:
+
+  - Location information specified in Geographic Markup Language
+    (GML) or civicAddress formats.
+
+  - A URI that points to externally hosted location information.
+
+Profile objects contain instructions for the disposition of location
+information, an optional reference to a Location object, and updates or
+overrides to that Location object if specified.
+
+Channel drivers and the dialplan functions are responsible for
+associating Profiles to endpoints/devices and calls.  Normally, two
+profiles would be assigned to an endpoint to control behavior in each
+direction and to optionally specify location information.  One for
+incoming calls (Asterisk is the UAS) and and one for outgoing calls
+(Asterisk is the UAC).
+
+NOTE:
+
+See https://wiki.asterisk.org/wiki/display/AST/Geolocation for the most
+complete and up-to-date information on valid values for the object
+parameters and a full list of references.
+
+GENERAL CAUTION:  You must coordinate with your partners with regards
+to what location information is expected by each party and how it should
+be formatted.  An outgoing configuration mismatch for instance, could
+result in misinformation or no information being sent to an emergency
+response center or even call failure for which you are solely responsible.
+--;
+
+
+;--
+=======================================================================
+  Location Object Description
+=======================================================================
+[<location_id>]
+
+-- type (required) ----------------------------------------------------
+Defines the object type.
+type = location
+
+Must be "location" to identify this configuration section as a
+Geolocation Location object.
+
+-- format (required) --------------------------------------------------
+Sets the format used to express the location.
+format = < civicAddress | GML | URI >
+
+Values:
+civicAddress: [RFC4119] [RFC5139] [RFC5491]
+              The location information will be placed in an XML document
+              conforming to the PIDF-LO standard.
+              For chan_pjsip, this will be placed in the body of
+              outgoing INVITE messages in addition to any SDP.
+
+GML:          [RFC4119] [RFC5491] [GeoShape]
+              The location information will be placed in an XML document
+              conforming to the PIDF-LO standard.
+              For chan_pjsip, this will be placed in the body of
+              outgoing INVITE messages in addition to any SDP.
+
+URI:          [RFC6442]
+              The external URI at which the the location information
+              can be found.  For chan_pjsip, this URI will be placed
+              in a "Geolocation" header in outgoing INVITE messages.
+
+There is no default.
+
+Example:
+format = civicAddress
+
+-- location_info (required) -------------------------------------------
+The location-format-specific information describing the location.
+location_info = <location_format_specific_description>
+
+For readability, multiple "location" parameters can be specified and
+they will be concatenated into one specification.  The description may
+contain replacement variables which may be the names of common channel
+variables like ${EXTEN}, channel variables you may have added in the
+dialplan, or variables you may have specified in the profile that
+references this location object.
+
+NOTE: See https://wiki.asterisk.org/wiki/display/AST/Geolocation for the
+most complete and up-to-date information on valid values for the object
+parameters and a full list of references.
+
+WARNING: Asterisk can only validate that a particular sub-parameter
+name is valid for a particular format. It can't validate the actual
+value of the sub-parameter.
+
+Example for civicAddress:
+
+location_info = country=US
+location_info = A1="New York", A3="New York", A4=Manhattan,  
+location_info = HNO=1633, PRD=W, RD=46th, STS=Street  
+location_info = PC=10222
+
+Example for GML with replacement variables:
+
+location_info = type=Point, crs=2d, pos="${mylat} ${mylon}"
+
+Example for URI with replacement variables:
+location_info = URI=https://some.company.com?number=${phone_number}
+
+-- method (optional) --------------------------------------------------
+The method used to determine the location_info
+method = <"GPS" | "A-GPS" | "Manual" | "DHCP"
+              | "Triangulation" | "Cell" | "802.11">
+
+Example:
+method = Manual
+
+-- location_source (optional) -----------------------------------------
+Original source of the location-info.
+location_source = < FQDN >
+
+The value MUST be a FQDN.  IP addresses are specifically not
+allowed.  See RFC8787.
+
+Example:
+location_source = sip1.myserver.net
+
+-- confidence (optional) -----------------------------------------
+The confidence in the location specified.
+confidence = pdf=[ unknown | normal | rectangular ], value=<percent_confident>
+
+Please see RFC7459 for the exact description of this parameter.
+
+Example:
+confidence = pdf=normal, value=75
+
+
+-- Location Example ---------------------------------------------------
+
+[mylocation]
+type = location
+format = civicAddress
+location_info = country=US
+location_info = A1="New York", A3="New York", A4=Manhattan
+location_info = HNO=1633, PRD=W, RD=46th, STS=Street
+location_info = PC=10222
+method = Manual
+location_source = sip1.myserver.net
+
+=======================================================================
+--;
+
+
+;--
+=======================================================================
+  Profile Object Descriptions
+=======================================================================
+[<profile_id>]
+
+-- type (required) ----------------------------------------------------
+Defines the object type.
+type = profile
+
+-- profile_precedence (optional) --------------------------------------
+Sets how to reconcile incoming and configured profiles.
+
+profile_precedence = < prefer_incoming | prefer_config | discard_incoming
+    | discard_config >
+
+On an incoming call leg, "incoming" is the location description
+received in the SIP INVITE (if any) and "config" is this profile.
+
+On an outgoing call leg, "incoming" is the location description
+passed through the dialplan to this channel (if any) and "config"
+is this profile.
+
+Values:
+
+prefer_incoming:  If there's an incoming location description, use it
+                  even if there's also a configured one.
+prefer_config:    If there's a configured location description, use it
+                  even if there's also an incoming one.
+discard_incoming: Discard any incoming location description. If there's
+                  a configured one, use it.  If not, no location
+                  information is propagated.
+discard_config:   Discard any configured location description. If
+                  there's an incoming one, use it.  If not, no location
+                  information is propagated.
+
+discard_incoming is the default.
+
+Example:
+profile_precedence = prefer_config
+
+-- pidf_element (optional) --------------------------------------------
+PIDF-LO element in which to place the location description.
+
+pidf_element = < tuple | device | person >
+Default: device
+
+If the format is civicAddress or GML, this sets the PIDF element into
+which the location information will be placed.
+
+Values:
+tuple:  Places the information in a "tuple" element.
+device: Places the information in a "device" element.
+person: Places the information in a "person" element.
+
+Per [RFC5491], "device" is preferred and therefore the default.
+
+Example:
+pidf_element = tuple
+
+-- allow_routing_use (optional) ---------------------------------------
+Sets whether the "Geolocation-Routing" header is added to outgoing
+requests.
+
+allow_routing_use = < yes | no >
+Default: no
+
+Set to "yes" to indicate that servers later in the path
+can use the location information for routing purposes.  Set to "no"
+if they should not.  If this value isn't specified, no
+"Geolocation-Routing" header will be added.
+
+Example:
+allow_routing_use = yes
+
+-- location_reference (optional) --------------------------------------
+The name of an existing Location object.
+location_reference = <location_id>
+
+The location_info_refinement and location_variables parameters below can
+be used to refine the location object for this specific profile.
+
+Example:
+location_reference = "my_building"
+
+-- location_info_refinement (optional) --------------------------------
+Location info to add to that already retrieved from the location object.
+
+location_info_refinement = <location_format_specific_description>
+
+The information in the referenced Location object can be refined on a
+per-profile basis.  For example, if the referenced Location object has a
+civicAddress for a building, you could set location_refinement to add a
+floor and room just for this profile
+
+Example:
+location_info_refinement = floor=20, room=20a2
+
+-- location_variables (optional) --------------------------------------
+
+If the referenced Location object uses any replacement variables, they
+can be assigned here.  There is no need to define variables that come
+from the channel using this profile.  They get assigned automatically.
+
+location_variables = myfloor=20, myroom=222
+
+-- suppress_empty_ca_elements (optional) ------------------------------
+Sets whether empty values for Civic Address elements should be
+suppressed from the outgoing PIDF-LO document.
+
+suppress_empty_ca_elements = < yes | no >
+Default: no
+
+Setting to "yes" allows you to define a location info template
+with channel variables that may or may not exist.
+
+For example, with:
+location_info_refinement = FLR=${MyFlr}
+suppress_empty_ca_elements = no ; the default
+
+If the MyFlr channel variable weren't set, the outgoing PIDF-LO document
+would have an empty <FLR/> element in it.  If suppress_empty_ca_elements
+were set to "yes", the FLR element would be dropped from the PIDF-LO
+document altogether.
+
+-- format, location_info, location_source, method, confidence ---------
+You can specify the location object's format, location_info,
+method, location_source and confidence parameters directly on
+a profile object for simple scenarios where the location
+information isn't common with any other profiles.  This is
+mutually exclusive with setting location_reference on the
+profile.
+
+-- Profile Example ----------------------------------------------------
+
+[myprofile]
+type = profile
+location_reference = mylocation
+location_info_refinement = floor=20, room=20a2
+pidf_element = tuple
+profile_action = discard_incoming
+
+=======================================================================
+--;
+
+-- NOTE ---------------------------------------------------------------
+There are 4 built-in profiles that can be assigned to endpoints:
+  "<prefer_config>"
+  "<discard_config>"
+  "<prefer_incoming>"
+  "<discard_incoming>"
+The profiles are empty except for having their precedence
+set.
+
diff --git a/configs/samples/hep.conf.sample b/configs/samples/hep.conf.sample
index 48586445e44f55122846ac5ea4b22597ae729eb3..db39bed26e7da32fe8941565a8ba5b464efa7cb1 100644
--- a/configs/samples/hep.conf.sample
+++ b/configs/samples/hep.conf.sample
@@ -22,6 +22,9 @@ capture_password = foo             ; If specified, the authorization password
 capture_id = 1234                  ; A unique integer identifier for this
                                    ; server. This ID will be embedded sent
                                    ; with each packet from this server.
+;capture_name = asterisk           ; A unique string identifier for this
+                                   ; server. This ID will be embedded sent
+                                   ; with each packet from this server.
 uuid_type = call-id                ; Specify the preferred source for the Homer
                                    ; correlation UUID. Valid options are:
                                    ; - 'call-id' for the PJSIP or chan_sip SIP
diff --git a/configs/samples/iax.conf.sample b/configs/samples/iax.conf.sample
index 1d1c136edcd8d6ae37816969fe4c1655b2778660..5dee369724dd1fea4e00ada1aa0e754b8304d3f3 100644
--- a/configs/samples/iax.conf.sample
+++ b/configs/samples/iax.conf.sample
@@ -386,7 +386,7 @@ autokill=yes
 ; IAX2 clients which request it.  This has only been used for the IAXy,
 ; and it has been recently proven that this firmware distribution method
 ; can be used as a source of traffic amplification attacks.  Also, the
-; IAXy firmware has not been updated for at least 18 months, so unless
+; IAXy firmware has not been updated since at least 2012, so unless
 ; you are provisioning IAXys in a secure network, we recommend that you
 ; leave this option to the default, off.
 ;
diff --git a/configs/samples/logger.conf.sample b/configs/samples/logger.conf.sample
index 27233268d816190787aba64a44d2a8a36c43b9cb..d046708f4faa8cbbde7dafe18ae370683abfd509 100644
--- a/configs/samples/logger.conf.sample
+++ b/configs/samples/logger.conf.sample
@@ -168,15 +168,15 @@
 ; modes on a production system unless you are in the process of debugging
 ; a specific issue.
 ;
-;debug => debug
-;trace => trace
-;security => security
+;debug.log => error,warning,notice,verbose,debug
+;trace.log => trace
+;security.log => security
 console => notice,warning,error
 ;console => notice,warning,error,debug
-messages => notice,warning,error
-;full => notice,warning,error,debug,verbose,dtmf,fax
+messages.log => notice,warning,error
+;full.log => notice,warning,error,debug,verbose,dtmf,fax
 ;
-;full-json => [json]debug,verbose,notice,warning,error,dtmf,fax
+;full-json.log => [json]debug,verbose,notice,warning,error,dtmf,fax
 ;
 ;syslog keyword : This special keyword logs to syslog facility
 ;
diff --git a/configs/samples/misdn.conf.sample b/configs/samples/misdn.conf.sample
deleted file mode 100644
index ca27c03bdbaf69df1390d83b83ea17f694e7671e..0000000000000000000000000000000000000000
--- a/configs/samples/misdn.conf.sample
+++ /dev/null
@@ -1,537 +0,0 @@
-;
-; chan_misdn sample config
-;
-
-; general section:
-;
-; for debugging and general setup, things that are not bound to port groups
-;
-
-[general]
-;
-; Sets the Path to the misdn-init.conf (for nt_ptp mode checking)
-;
-misdn_init=/etc/misdn-init.conf
-
-; set debugging flag:
-;   0 - No Debug
-;   1 - mISDN Messages and * - Messages, and * - State changes
-;   2 - Messages + Message specific Informations (e.g. bearer capability)
-;   3 - very Verbose, the above + lots of Driver specific infos
-;   4 - even more Verbose than 3
-;
-; default value: 0
-;
-debug=0
-
-
-
-; set debugging file and flags for mISDNuser (NT-Stack)
-;
-; flags can be or'ed with the following values:
-;
-; DBGM_NET        0x00000001
-; DBGM_MSG        0x00000002
-; DBGM_FSM        0x00000004
-; DBGM_TEI        0x00000010
-; DBGM_L2         0x00000020
-; DBGM_L3         0x00000040
-; DBGM_L3DATA     0x00000080
-; DBGM_BC         0x00000100
-; DBGM_TONE       0x00000200
-; DBGM_BCDATA     0x00000400
-; DBGM_MAN        0x00001000
-; DBGM_APPL       0x00002000
-; DBGM_ISDN       0x00004000
-; DBGM_SOCK       0x00010000
-; DBGM_CONN       0x00020000
-; DBGM_CDATA      0x00040000
-; DBGM_DDATA      0x00080000
-; DBGM_SOUND      0x00100000
-; DBGM_SDATA      0x00200000
-; DBGM_TOPLEVEL   0x40000000
-; DBGM_ALL        0xffffffff
-;
-
-ntdebugflags=0
-ntdebugfile=/var/log/misdn-nt.log
-
-
-; some pbx systems do cut the L1 for some milliseconds, to avoid
-; dropping running calls, we can set this flag to yes and tell
-; mISDNuser not to drop the calls on L2_RELEASE
-ntkeepcalls=no
-
-; the big trace
-;
-; default value: [not set]
-;
-;tracefile=/var/log/asterisk/misdn.log
-
-
-; set to yes if you want mISDN_dsp to bridge the calls in HW
-;
-; default value: yes
-;
-bridging=no
-
-
-; stops dialtone after getting first digit on nt Port
-;
-; default value: yes
-;
-stop_tone_after_first_digit=yes
-
-; whether to append overlapdialed Digits to Extension or not
-;
-; default value: yes
-;
-append_digits2exten=yes
-
-;;; CRYPTION STUFF
-
-; Whether to look for dynamic crypting attempt
-;
-; default value: no
-;
-dynamic_crypt=no
-
-; crypt_prefix, what is used for crypting Protocol
-;
-; default value: [not set]
-;
-crypt_prefix=**
-
-; Keys for cryption, you reference them in the dialplan
-; later also in dynamic encr.
-;
-; default value: [not set]
-;
-crypt_keys=test,muh
-
-; ----------------------------- JITTER BUFFER CONFIGURATION --------------------------
-; jbenable = yes              ; Enables the use of a jitterbuffer on the receiving side of a
-                              ; SIP channel. Defaults to "no". An enabled jitterbuffer will
-                              ; be used only if the sending side can create and the receiving
-                              ; side can not accept jitter. The SIP channel can accept jitter,
-                              ; thus a jitterbuffer on the receive SIP side will be used only
-                              ; if it is forced and enabled.
-
-; jbforce = no                ; Forces the use of a jitterbuffer on the receive side of a SIP
-                              ; channel. Defaults to "no".
-
-; jbmaxsize = 200             ; Max length of the jitterbuffer in milliseconds.
-
-; jbresyncthreshold = 1000    ; Jump in the frame timestamps over which the jitterbuffer is
-                              ; resynchronized. Useful to improve the quality of the voice, with
-                              ; big jumps in/broken timestamps, usually sent from exotic devices
-                              ; and programs. Defaults to 1000.
-
-; jbimpl = fixed              ; Jitterbuffer implementation, used on the receiving side of a SIP
-                              ; channel. Two implementations are currently available - "fixed"
-                              ; (with size always equals to jbmaxsize) and "adaptive" (with
-                              ; variable size, actually the new jb of IAX2). Defaults to fixed.
-
-; jbtargetextra = 40          ; This option only affects the jb when 'jbimpl = adaptive' is set.
-                              ; The option represents the number of milliseconds by which the new
-                              ; jitter buffer will pad its size. the default is 40, so without
-                              ; modification, the new jitter buffer will set its size to the jitter
-                              ; value plus 40 milliseconds. increasing this value may help if your
-                              ; network normally has low jitter, but occasionally has spikes.
-
-; jblog = no                  ; Enables jitterbuffer frame logging. Defaults to "no".
-; ----------------------------------------------------------------------------------
-
-; users sections:
-;
-; name your sections as you wish but not "general" or "default" !
-; the sections are Groups, you can dial out in extensions.conf
-; with Dial(mISDN/g:extern/101) where extern is a section name,
-; chan_misdn tries every port in this section to find a
-; new free channel
-;
-; The default section is not a group section, it just contains config elements
-; which are inherited by group sections.
-;
-[default]
-
-; define your default context here
-;
-; default value: default
-;
-context=misdn
-
-; language
-;
-; default value: en
-;
-language=en
-
-;
-; This option specifies a default music on hold class to
-; use when put on hold if the channel's moh class was not
-; explicitly set with Set(CHANNEL(musicclass)=whatever) and
-; the peer channel did not suggest a class to use.
-;
-musicclass=default
-
-;
-; Either if we should produce DTMF Tones ourselves
-;
-senddtmf=yes
-
-;
-; If we should generate Ringing for chan_sip and others
-;
-far_alerting=no
-
-
-;
-; Here you can list which bearer capabilities should be allowed:
-;   all                  - allow any bearer capability
-;   speech               - allow speech
-;   3_1khz               - allow 3.1KHz audio
-;   digital_unrestricted - allow unrestricted digital
-;   digital_restricted   - allow restricted digital
-;   video                - allow video
-;
-; Example:
-; allowed_bearers=speech,3_1khz
-;
-allowed_bearers=all
-
-; Incoming number prefixes for the indicated Type-Of-Number.  These are
-; inserted before any number (caller, dialed, connected, redirecting,
-; redirection) received from the ISDN link if that number has the
-; corresponding Type-Of-Number.
-; See the dialplan options.
-;
-; default values:
-;    unknownprefix=
-;    internationalprefix=00
-;    nationalprefix=0
-;    netspecificprefix=
-;    subscriberprefix=
-;    abbreviatedprefix=
-;
-;unknownprefix=
-internationalprefix=00
-nationalprefix=0
-;netspecificprefix=
-;subscriberprefix=
-;abbreviatedprefix=
-
-; set rx/tx gains between -8 and 8 to change the RX/TX Gain
-;
-; default values: rxgain: 0
-;                 txgain: 0
-;
-rxgain=0
-txgain=0
-
-; some telcos especially in NL seem to need this set to yes, also in
-; switzerland this seems to be important
-;
-; default value: no
-;
-te_choose_channel=no
-
-
-
-;
-; Monitors L1 of the port.  If L1 is down it tries
-; to bring it up.  The polling timeout is given in seconds.
-; Setting the value to 0 disables monitoring L1 of the port.
-;
-; default value: 0
-;
-; This option is only read at chan_misdn loading time.
-; You need to unload and load chan_misdn to change the
-; value.  An asterisk restart will also do the trick.
-;
-l1watcher_timeout=0
-
-;
-; This option defines, if chan_misdn should check the L1 on  a PMP
-; before making a group call on it. The L1 may go down for PMP Ports
-; so we might need this.
-; But be aware! a broken or plugged off cable might be used for a group call
-; as well, since chan_misdn has no chance to distinguish if the L1 is down
-; because of a lost Link or because the Provider shut it down...
-;
-; default: no
-;
-pmp_l1_check=no
-
-
-;
-; in PMP this option defines which cause should be sent out to
-; the 3. caller. chan_misdn does not support callwaiting on TE
-; PMP side. This allows to modify the RELEASE_COMPLETE cause
-; at least.
-;
-reject_cause=16
-
-
-;
-; Send Setup_Acknowledge on incoming calls anyway (instead of PROCEEDING),
-; this requests additional Infos, so we can waitfordigits
-; without much issues. This works only for PTP Ports
-;
-; default value: no
-;
-need_more_infos=no
-
-
-;
-; set this to yes if you want to disconnect calls when a timeout occurs
-; for example during the overlapdial phase
-;
-nttimeout=no
-
-; Set the method to use for channel selection:
-;   standard     - Use the first free channel starting from the lowest number.
-;   standard_dec - Use the first free channel starting from the highest number.
-;   round_robin  - Use the round robin algorithm to select a channel. Use this
-;                  if you want to balance your load.
-;
-; default value: standard
-;
-method=standard
-
-
-; specify if chan_misdn should collect digits before going into the
-; dialplan, you can choose yes=4 Seconds, no, or specify the amount
-; of seconds you need;
-;
-overlapdial=yes
-
-;
-; dialplan means Type Of Number in ISDN Terms
-; There are different types of the dialplan:
-;
-; dialplan -> for outgoing call's dialed number
-; localdialplan -> for outgoing call's callerid
-;       (if -1 is set use the value from the asterisk channel)
-; cpndialplan -> for incoming call's connected party number sent to caller
-;       (if -1 is set use the value from the asterisk channel)
-;
-; dialplan options:
-;
-; 0 - unknown
-; 1 - International
-; 2 - National
-; 3 - Network-Specific
-; 4 - Subscriber
-; 5 - Abbreviated
-;
-; default value: 0
-;
-dialplan=0
-localdialplan=0
-cpndialplan=0
-
-
-
-;
-; turn this to no if you don't mind correct handling of Progress Indicators
-;
-early_bconnect=yes
-
-
-;
-; turn this on if you like to send Tone Indications to a Incoming
-; isdn channel on a TE Port. Rarely used, only if the Telco allows
-; you to send indications by yourself, normally the Telco sends the
-; indications to the remote party.
-;
-; default: no
-;
-incoming_early_audio=no
-
-; uncomment the following to get into s extension at extension conf
-; there you can use DigitTimeout if you can't or don't want to use
-; isdn overlap dial.
-; note: This will jump into the s exten for every exten!
-;
-; default value: no
-;
-;always_immediate=no
-
-;
-; set this to yes if you want to generate your own dialtone
-; with always_immediate=yes, else chan_misdn generates the dialtone
-;
-; default value: no
-;
-nodialtone=no
-
-
-; uncomment the following if you want callers which called exactly the
-; base number (so no extension is set) jump to the s extension.
-; if the user dials something more it jumps to the correct extension
-; instead
-;
-; default value: no
-;
-;immediate=no
-
-; uncomment the following to have hold and retrieve support
-;
-; default value: no
-;
-;hold_allowed=yes
-
-; Pickup and Callgroup
-;
-; default values: not set = 0
-; range: 0-63
-;
-;callgroup=1
-;pickupgroup=1
-
-; Named pickup groups and named call groups
-;
-; give a name to groups and configure any number of groups
-;
-;namedcallgroup=engineering,sales,netgroup,protgroup
-;namedpickupgroup=sales
-
-; Set the outgoing caller id to the value.
-;callerid="name" <number>
-
-;
-; these are the exact isdn screening and presentation indicators
-; if -1 is given for either value the presentation indicators are used
-; from asterisks CALLERPRES function.
-; s=0, p=0 -> callerid presented
-; s=1, p=1 -> callerid restricted (the remote end does not see it!)
-;
-; default values s=-1, p=-1
-presentation=-1
-screen=-1
-
-; Incoming calls will have a caller ID tag set to this value
-;
-;incoming_cid_tag = "asterisk"
-
-; With this set, you can automatically append the MSN of a party
-; to the cid_tag. Incoming calls have the dialed number appended
-; to the tag, and outgoing calls have the caller number appended
-; to the tag. An '_' is used to separate the tag from the
-; MSN.
-; Default is no.
-;
-;append_msn_to_cid_tag = no
-
-; Select what to do with outgoing COLP information on this port.
-;
-; 0 - Send out COLP information unaltered. (default)
-; 1 - Force COLP to restricted on all outgoing COLP information.
-; 2 - Do not send COLP information.
-outgoing_colp=0
-
-; Put a display ie in the CONNECT message containing the following
-; information if it is available (nt port only):
-;
-; 0 - Do not put the connected line information in the display ie.
-; 1 - Put the available connected line name in the display ie.
-; 2 - Put the available connected line number in the display ie.
-; 3 - Put the available connected line name and number in the display ie.
-;
-display_connected=0
-
-; Put a display ie in the SETUP message containing the following
-; information if it is available (nt port only):
-;
-; 0 - Do not put the caller information in the display ie.
-; 1 - Put the available caller name in the display ie.
-; 2 - Put the available caller number in the display ie.
-; 3 - Put the available caller name and number in the display ie.
-;
-display_setup=0
-
-; This enables echo cancellation with the given number of taps.
-; Be aware: Move this setting only to outgoing portgroups!
-; A value of zero turns echo cancellation off.
-;
-; possible values are: 0,32,64,128,256,yes(=128),no(=0)
-;
-; default value: no
-;
-;echocancel=no
-
-;
-; chan_misdns jitterbuffer, default 4000
-;
-jitterbuffer=4000
-
-;
-; change this threshold to enable dejitter functionality
-;
-jitterbuffer_upper_threshold=0
-
-
-;
-; change this to yes, if you want to bridge a mISDN data channel to
-; another channel type or to an application.
-;
-hdlc=no
-
-
-;
-; defines the maximum amount of incoming calls per port for
-; this group. Calls which exceed the maximum will be marked with
-; the channel variable MAX_OVERFLOW. It will contain the amount of
-; overflowed calls
-;
-max_incoming=-1
-
-;
-; defines the maximum amount of outgoing calls per port for this group
-; exceeding calls will be rejected
-;
-max_outgoing=-1
-
-;
-; Enable/disable the call-completion retention option support (ptp only).
-;
-; Note: To use the CCBS/CCNR supplementary service feature and other
-; supplementary services using FACILITY messages requires a
-; modified version of mISDN from:
-; http://svn.digium.com/svn/thirdparty/mISDN/trunk
-; http://svn.digium.com/svn/thirdparty/mISDNuser/trunk
-;
-cc_request_retention=yes
-
-[intern]
-; define your ports, e.g. 1,2 (depends on mISDN-driver loading order)
-ports=1,2
-; context where to go to when incoming Call on one of the above ports
-context=Intern
-
-[internPP]
-;
-; adding the postfix 'ptp' to a port number is obsolete now, chan_misdn
-; parses /etc/misdn-init.conf and sets the ptp mode to the corresponding
-; configs. For backwards compatibility you can still set ptp here.
-;
-ports=3
-
-[first_extern]
-; again port defs
-ports=4
-; again a context for incoming calls
-context=Extern1
-; msns for te ports, listen on those numbers on the above ports, and
-; indicate the incoming calls to asterisk
-; here you can give a comma separated list or simply an '*' for
-; any msn.
-msns=*
-
-; here an example with given msns
-[second_extern]
-ports=5
-context=Extern2
-callerid="Asterisk" <1234>
-msns=102,144,101,104
diff --git a/configs/samples/modules.conf.sample b/configs/samples/modules.conf.sample
index 56ffe27eee3bbfb049ef20dcc0638483af25a2d7..2ab16d1fd0286000e1ed60582ef72d76f1a7b510 100644
--- a/configs/samples/modules.conf.sample
+++ b/configs/samples/modules.conf.sample
@@ -31,12 +31,11 @@ autoload=yes
 ;
 ;load = res_musiconhold.so
 ;
-; Load one of: chan_oss, alsa, or console (portaudio).
-; By default, load chan_oss only (automatically).
+; Load one of: alsa, or console (portaudio).
+; By default, load chan_console only (automatically).
 ;
 noload = chan_alsa.so
-noload = chan_console.so
-;noload = chan_oss.so
+;noload = chan_console.so
 ;
 ; Do not load res_hep and kin unless you are using HEP monitoring
 ; <http://sipcapture.org> in your network.
diff --git a/configs/samples/musiconhold.conf.sample b/configs/samples/musiconhold.conf.sample
index 2f601086b096847eafbeb133520c900b238e87c0..1737c7a170caeebf1247c8fea7f54bd05eec6fb0 100644
--- a/configs/samples/musiconhold.conf.sample
+++ b/configs/samples/musiconhold.conf.sample
@@ -82,6 +82,9 @@ directory=moh
 ;               ; in alphabetical order. If 'randstart', the files are sorted
 ;               ; in alphabetical order as well, but the first file is chosen
 ;               ; at random. If unspecified, the sort order is undefined.
+;answeredonly=yes       ; Only allow answered channels to have music on hold.
+                        ; Enabling this will prevent MOH on unanswered channels.
+                        ; (default: "no")
 
 ;[native-alphabetical]
 ;mode=files
diff --git a/configs/samples/muted.conf.sample b/configs/samples/muted.conf.sample
deleted file mode 100644
index 15c7409d9b2e243ae46c0dcc1bfdf3acbfeef429..0000000000000000000000000000000000000000
--- a/configs/samples/muted.conf.sample
+++ /dev/null
@@ -1,39 +0,0 @@
-#
-# Sample muted configuration file
-#
-# Copyright (C) 2004 Digium, Inc.
-#
-# First you have the host, username, and password
-# we use to connect to the asterisk system
-#
-# What is this? Well, haven't you ever wished you could automatically
-# turn down the volume on your stereo, CDPlayer, etc, when a call comes in,
-# and then return it to normal when the call ends? Well, this is a possible
-# mechanism to make this happen!
-# You have to fire up the new utils/muted, which runs as a daemon in the
-# background. This daemon connects to asterisk via a manager interface, and
-# also reads this config file from /etc/muted.conf. when the channels mentioned
-# are activated, it tweaks the sound levels on the sound card(s).
-# So, depending on the sound card, you may be able to run all your sound
-# generating devices thru your sound card, and use this mechanism to quiet
-# them down while you are on the phone. If anyone figures out how to make
-# this work with kids, please inform!!
-#
-host localhost
-user user
-pass pass
-#
-# List each channel we're supposed to watch
-#
-channel DAHDI/1
-channel DAHDI/2
-channel SIP/mark
-#
-# Mute level is the percentage of the current volume we should
-# lower the music to.
-#
-mutelevel 20
-#
-# Smooth fade makes the fadein/fadeout nicer sounding
-#
-smoothfade
diff --git a/configs/samples/oss.conf.sample b/configs/samples/oss.conf.sample
deleted file mode 100644
index b0b3831ab6f897eaba6531eb02bc6f621352b2aa..0000000000000000000000000000000000000000
--- a/configs/samples/oss.conf.sample
+++ /dev/null
@@ -1,152 +0,0 @@
-;
-; Automatically generated from ../channels/chan_oss.c
-;
-
-[general]
-    ; General config options, with default values shown.
-    ; You should use one section per device, with [general] being used
-    ; for the first device and also as a template for other devices.
-    ;
-    ; All but 'debug' can go also in the device-specific sections.
-    ;
-    ; debug = 0x0		; misc debug flags, default is 0
-
-    ; Set the device to use for I/O
-    ; device = /dev/dsp
-
-    ; Optional mixer command to run upon startup (e.g. to set
-    ; volume levels, mutes, etc.
-    ; mixer =
-
-    ; Software mic volume booster (or attenuator), useful for sound
-    ; cards or microphones with poor sensitivity. The volume level
-    ; is in dB, ranging from -20.0 to +20.0
-    ; boost = n			; mic volume boost in dB
-
-    ; Set the callerid for outgoing calls
-    ; callerid = John Doe <555-1234>
-
-    ; autoanswer = no		; no autoanswer on call
-    ; autohangup = yes		; hangup when other party closes
-    ; extension = s		; default extension to call
-    ; context = default		; default context for outgoing calls
-    ; language = ""		; default language
-
-    ; If you set overridecontext to 'yes', then the whole dial string
-    ; will be interpreted as an extension, which is extremely useful
-    ; to dial SIP, IAX and other extensions which use the '@' character.
-    ; The default is 'no' just for backward compatibility, but the
-    ; suggestion is to change it.
-    ; overridecontext = no	; if 'no', the last @ will start the context
-				; if 'yes' the whole string is an extension.
-
-    ; low level device parameters in case you have problems with the
-    ; device driver on your operating system. You should not touch these
-    ; unless you know what you are doing.
-    ; queuesize = 10		; frames in device driver
-    ; frags = 8			; argument to SETFRAGMENT
-
-    ; ----------------------------- JITTER BUFFER CONFIGURATION --------------------------
-    ; jbenable = yes              ; Enables the use of a jitterbuffer on the receiving side of an
-                                  ; OSS channel. Defaults to "no". An enabled jitterbuffer will
-                                  ; be used only if the sending side can create and the receiving
-                                  ; side can not accept jitter. The OSS channel can't accept jitter,
-                                  ; thus an enabled jitterbuffer on the receive OSS side will always
-                                  ; be used if the sending side can create jitter.
-
-    ; jbmaxsize = 200             ; Max length of the jitterbuffer in milliseconds.
-
-    ; jbresyncthreshold = 1000    ; Jump in the frame timestamps over which the jitterbuffer is
-                                  ; resynchronized. Useful to improve the quality of the voice, with
-                                  ; big jumps in/broken timestamps, usually sent from exotic devices
-                                  ; and programs. Defaults to 1000.
-
-    ; jbimpl = fixed              ; Jitterbuffer implementation, used on the receiving side of an OSS
-                                  ; channel. Two implementations are currently available - "fixed"
-                                  ; (with size always equals to jbmax-size) and "adaptive" (with
-                                  ; variable size, actually the new jb of IAX2). Defaults to fixed.
-
-    ; jbtargetextra = 40          ; This option only affects the jb when 'jbimpl = adaptive' is set.
-                                  ; The option represents the number of milliseconds by which the new
-                                  ; jitter buffer will pad its size. the default is 40, so without
-                                  ; modification, the new jitter buffer will set its size to the jitter
-                                  ; value plus 40 milliseconds. increasing this value may help if your
-                                  ; network normally has low jitter, but occasionally has spikes.
-
-    ; jblog = no                  ; Enables jitterbuffer frame logging. Defaults to "no".
-    ; ----------------------------------------------------------------------------------
-
-; below is an entry for a second console channel
-; [card1]
-    ; device = /dev/dsp1	; alternate device
-
-; Below are the settings to support video. You can include them
-; in your general configuration as [general](+,video)
-; The parameters are all available through the CLI as "console name value"
-; Section names used here are only examples.
-
-[my_video](!)      ; you can just include in your config
-    videodevice = /dev/video0	; uses your V4L webcam as video source
-    videodevice = X11		; X11 grabber. Dragging on the local display moves the origin.
-    videocodec = h263		; also h261, h263p, h264, mpeg4, ...
-
-    ; video_size is the geometry used by the encoder.
-    ; Depending on the codec your choice is restricted.
-    video_size = 352x288	; the format WIDTHxHEIGHT is also ok
-    video_size = cif		; sqcif, qcif, cif, qvga, vga, ...
-
-    ; You can also set the geometry used for the camera, local display and remote display.
-    ; The local window is on the right, the remote window is on the left.
-    ; Right clicking with the mouse on a video window increases the size,
-    ; center-clicking reduces the size.
-    camera_size = cif
-    remote_size = cif
-    local_size = qcif
-
-    bitrate = 60000             ; rate told to ffmpeg.
-    fps = 5                     ; frames per second from the source.
-    ; qmin = 3                  ; quantizer value passed to the encoder.
-
-; The keypad is made of an image (in any format supported by SDL_image)
-; and some configuration entries indicating the location and function of buttons.
-; These entries can also be contained in the comment field of the image,
-; which is a lot more convenient to manage.
-; E.g. for jpeg you can write them with wrjpgcom (part of libjpeg).
-; The format to define keys is
-;	region = <event> <shape> x0 y0 x1 y1 h
-; where <event> is the event to be generated (a digit, pickup, hangup,...)
-; <shape> is the shape of the region (currently 'rect' and 'circle' are
-; supported, the latter is really an ellipse),  x0 y0 x1 y1 are the
-; coordinates of the base of the rectangle or main diameter of the ellipse,
-; (they can be rotated) while h is the height of the rectangle or the other
-; diameter of the ellipse.
-;
-[my_skin](!)
-    keypad = /tmp/keypad.jpg
-    region = 1 rect   19  18    67  18  28
-    region = 2 rect   84  18   133  18  28
-    region = 3 rect  152  18   201  18  28
-    region = 4 rect   19  60    67  60  28
-    region = 5 rect   84  60   133  60  28
-    region = 6 rect  152  60   201  60  28
-    region = 7 rect   19 103    67 103  28
-    region = 8 rect   84 103   133 103  28
-    region = 9 rect  152 103   201 103  28
-    region = * rect   19 146    67 146  28
-    region = 0 rect   84 146   133 146  28
-    region = # rect  152 146   201 146  28
-    region = pickup rect  229 15  267 15 40
-    region = hangup rect  230 66  270 64 40
-    region = mute circle  232 141 264 141 33
-    region = sendvideo circle  235 185 266 185 33
-    region = autoanswer rect 228 212 275 212 50
-
-; another skin with entries for the keypad and a small font
-; to write to the message boards in the skin.
-[skin2](!)
-    keypad = /tmp/kpad2.jpg
-    keypad_font = /tmp/font.png
-
-; to add video support, uncomment this and remember to install
-; the keypad and keypad_font files to the right place
-; [general](+,my_video,skin2)
diff --git a/configs/samples/phone.conf.sample b/configs/samples/phone.conf.sample
deleted file mode 100644
index 3d4a7c2dd99acde6a581191bb4acbd2fb6a9e837..0000000000000000000000000000000000000000
--- a/configs/samples/phone.conf.sample
+++ /dev/null
@@ -1,51 +0,0 @@
-;
-; Linux Telephony Interface
-;
-; Configuration file
-;
-[interfaces]
-;
-; Select a mode, either the phone jack provides dialtone, reads digits,
-; then starts PBX with the given extension (dialtone mode), or
-; immediately provides the PBX without reading any digits or providing
-; any dialtone (this is the immediate mode, the default).  Also, you
-; can set the mode to "fxo" if you have a linejack to make it operate
-; properly. If you are using a Sigma Designs board you may set this to
-; "sig".
-;
-mode=immediate
-;mode=dialtone
-;mode=fxo
-;mode=sig
-;
-; You can decide which format to use by default, "g723.1", "g729", or "slinear".
-; Note that g729 is only supported for Sigma Designs boards.
-; XXX Be careful, sometimes the card causes kernel panics when running
-; in signed linear mode for some reason... XXX
-;
-format=slinear
-;format=g723.1
-;format=g729
-;
-; And set the echo cancellation to "off", "low", "medium", and "high".
-; This is not supported on all phones.
-;
-echocancel=medium
-;
-; You can optionally use VAD/CNG silence suppression
-;
-;silencesupression=yes
-;
-; List all devices we can use.  Contexts may also be specified
-;
-;context=local
-;
-; You can set txgain and rxgain for each device in the same way as context.
-; If you want to change default gain value (1.0 =~ 100%) for device, simple
-; add txgain or rxgain line before device line. But remember, if you change
-; volume all cards listed below will be affected by these values. You can
-; use float values (1.0, 0.5, 2.0) or percentage values (100%, 150%, 50%).
-;
-;txgain=100%
-;rxgain=1.0
-;device => /dev/phone0
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index b1c1657ff7007e925ef231bedec4844cfa3c2fa7..572cf08e344f15a35a04aac4f647a5c6259dbddb 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -616,6 +616,8 @@
 ;aggregate_mwi=yes      ;  (default: "yes")
 ;allow= ; Media Codec s to allow (default: "")
 ;allow_overlap=yes ; Enable RFC3578 overlap dialing support. (default: "yes")
+;overlap_context=default ; Context to used for overlap dialing matches
+                         ; (default: same as context option)
 ;aors=  ; AoR s to be used with the endpoint (default: "")
 ;auth=  ; Authentication Object s associated with the endpoint (default: "")
 ;callerid=      ; CallerID information for the endpoint (default: "")
@@ -955,6 +957,22 @@
                            ; responses.
                            ; (default: no)
 
+;geoloc_incoming_call_profile =
+                ; This geolocation profile will be applied to all calls received
+                ; by the channel driver from the remote endpoint before they're
+                ; forwarded to the dialplan.
+;geoloc_outgoing_call_profile =
+                ; This geolocation profile will be applied to all calls received
+                ; by the channel driver from the dialplan before they're forwarded
+                ; the remote endpoint.
+;
+; send_aoc =
+                ; This options turns on and off support for sending AOC to endpoints.
+                ; AOC updates can be sent using the AOCMessage AMI action or come
+                ; from PRI channels.
+                ; (default: no)
+
+
 ;==========================AUTH SECTION OPTIONS=========================
 ;[auth]
 ;  SYNOPSIS: Authentication type
@@ -1025,8 +1043,6 @@
 ;[transport]
 ;  SYNOPSIS: SIP Transport
 ;
-;async_operations=1     ; Number of simultaneous Asynchronous Operations
-                        ; (default: "1")
 ;bind=  ; IP Address and optional port to bind to for this transport (default:
         ; "")
 ; Note that for the Websocket transport the TLS configuration is configured
@@ -1045,11 +1061,16 @@
                 ; and/or "asterisk_ecc.pem" are loaded (certificate, inter-
                 ; mediates, private key), to support multiple algorithms for
                 ; server authentication (RSA, DSA, ECDSA). If the chains are
-                ; different, at least OpenSSL 1.0.2 is required.
+                ; different, at least OpenSSL 1.0.2 is required. This option
+                ; can be reloaded resulting in an updated certificate if the
+                ; filename remains unchanged.
                 ; (default: "")
 ;cipher=        ; Preferred cryptography cipher names TLS ONLY (default: "")
 ;method=        ; Method of SSL transport TLS ONLY (default: "")
-;priv_key_file= ; Private key file TLS ONLY (default: "")
+;priv_key_file= ; Private key file TLS ONLY. This option can be reloaded
+                ; resulting in an updated private key if the filename remains
+                ; unchanged.
+                ; (default: "")
 ;verify_client= ; Require verification of client certificate TLS ONLY (default:
                 ; "")
 ;verify_server= ; Require verification of server certificate TLS ONLY (default:
@@ -1092,6 +1113,15 @@
                         ; URI is not a hostname, the saved transport will be
                         ; used and the 'x-ast-txp' parameter stripped from the
                         ; outgoing packet.
+;allow_wildcard_certs=no ; In conjunction with verify_server, if 'yes' allow use
+                         ; of wildcards, i.e. '*.' in certs for common, and
+                         ; subject alt names of type DNS for TLS transport
+                         ; types. Note, names must start with the wildcard.
+                         ; Partial wildcards, e.g. 'f*.example.com' and
+                         ; 'foo.*.com' are disallowed. As well, names only
+                         ; match against a single level meaning '*.example.com'
+                         ; matches 'foo.example.com', but not
+                         ; 'foo.bar.example.com'. Defaults to 'no'.
 
 ;==========================AOR SECTION OPTIONS=========================
 ;[aor]
@@ -1313,6 +1343,19 @@
                     ; creating an implicit subscription (see RFC 4488).
                     ; (default: "yes")
 
+;all_codecs_on_empty_reinvite=yes
+                    ; On reception of a re-INVITE without SDP Asterisk will send an SDP
+                    ; offer in the 200 OK response containing all configured codecs on the
+                    ; endpoint, instead of simply those that have already been negotiated.
+                    ; RFC 3261 specifies this as a SHOULD requirement.
+                    ; (default: "no")
+
+;allow_sending_180_after_183=yes	; Allow Asterisk to send 180 Ringing to an endpoint
+					; after 183 Session Progress has been send.
+					; If disabled Asterisk will instead send only a
+					; 183 Session Progress to the endpoint.
+					; (default: "no")
+
 ; MODULE PROVIDING BELOW SECTION(S): res_pjsip_acl
 ;==========================ACL SECTION OPTIONS=========================
 ;[acl]
@@ -1346,6 +1389,11 @@
                 ; (default: "")
 ;outbound_proxy=        ; Proxy through which to send registrations, a full SIP URI
                         ; must be provided (default: "")
+;max_random_initial_delay=10    ; Maximum random delay for initial registrations (default: 10)
+                                ; Generally it is a good idea to space out registrations
+                                ; to not overload the system. If you have a small number
+                                ; of registrations and need them to register more quickly,
+                                ; you can reduce this to a lower value.
 ;retry_interval=60      ; Interval in seconds between retries if outbound
                         ; registration is unsuccessful (default: "60")
 ;forbidden_retry_interval=0     ; Interval used when receiving a 403 Forbidden
@@ -1553,3 +1601,16 @@
 
 ;mailbox_state_filter=     ; Optional regular expression used to filter what
                            ; mailboxes we accept events for.
+
+
+;================================TEL URIs=====================================
+;
+; Asterisk has TEL URI support, but with limited scope. Support is only for
+; TEL URIs present in traffic from a remote party. Asterisk does not generate
+; any TEL URIs of its own.
+;
+; Currently, the allowed request types are INVITE, ACK, BYE, and CANCEL. Any
+; other request type that contains a TEL URI will behave as it did before.
+; TEL URIs are allowed in the request, From, and To headers.
+;
+; You can match a TEL URI From header by IP, header, or auth_username.
diff --git a/configs/samples/queues.conf.sample b/configs/samples/queues.conf.sample
index aa7e03d34b133208256106aa509fc339db3e3a1e..fbb5653203be30caaf34087c817d0531f79ef0c9 100644
--- a/configs/samples/queues.conf.sample
+++ b/configs/samples/queues.conf.sample
@@ -64,8 +64,9 @@ monitor-type = MixMonitor
 ;
 ; Musicclass sets which music applies for this particular call queue.
 ; The only class which can override this one is if the MOH class is set
+; using the m option when calling the Queue application or if set
 ; directly on the channel using Set(CHANNEL(musicclass)=whatever) in the
-; dialplan.
+; dialplan (the latter of which overrides everything).
 ;
 ;musicclass = default
 ;
@@ -77,6 +78,12 @@ monitor-type = MixMonitor
 ;
 ;announce = queue-markq
 ;
+; An announcement may be specified which is played to the caller just
+; before they are bridged with an agent. The default is to not play an
+; announcement to the caller.
+;
+;queue-callerannounce = you-are-being-connected
+;
 ; A strategy may be specified.  Valid strategies include:
 ;
 ; ringall - ring all available channels until one answers (default)
@@ -149,7 +156,7 @@ monitor-type = MixMonitor
 ; Queue application is more important. In the scenario above, timeoutpriority=app
 ; would result in the second member's phone ringing for 1 second.
 ;
-; There are a few exceptions to the priority rules. For instance, if timeoutpriority=appp
+; There are a few exceptions to the priority rules. For instance, if timeoutpriority=app
 ; and the configuration file timeout is set to 0, but the application argument timeout is
 ; non-zero, then the timeoutpriority is ignored and the application argument is used as
 ; the timeout. Furthermore, if no application argument timeout is specified, then the
@@ -353,6 +360,10 @@ monitor-type = MixMonitor
 ;queue-thereare	= queue-thereare
 			;	("calls waiting.")
 ;queue-callswaiting = queue-callswaiting
+			;	("Currently there are more than")
+;queue-quantity1 = queue-quantity1
+			;	("callers waiting to speak with a representative")
+;queue-quantity2 = queue-quantity2
 			;	("The current est. holdtime is")
 ;queue-holdtime = queue-holdtime
 			;	("minute.")
diff --git a/configs/samples/res_config_sqlite.conf.sample b/configs/samples/res_config_sqlite.conf.sample
deleted file mode 100644
index 2d14d46a3dd27780cdc759d7af0897f690d9a2ca..0000000000000000000000000000000000000000
--- a/configs/samples/res_config_sqlite.conf.sample
+++ /dev/null
@@ -1,11 +0,0 @@
-[general]
-
-; The database file.
-dbfile => /var/lib/asterisk/sqlite.db
-
-; Both config_table and cdr_table are optional. If config_table is omitted,
-; you must specify it in extconfig.conf. If it is both provided here and in
-; extconfig.conf, the value given here is used. If cdr_table is omitted, CDR
-; support is simply disabled.
-config_table => ast_config
-; cdr_table => ast_cdr
diff --git a/configs/samples/res_http_media_cache.conf.sample b/configs/samples/res_http_media_cache.conf.sample
new file mode 100644
index 0000000000000000000000000000000000000000..925156bcbd998136d71e3c66ebf10ed9d86c2bde
--- /dev/null
+++ b/configs/samples/res_http_media_cache.conf.sample
@@ -0,0 +1,69 @@
+;
+; Sample configuration for res_http_media_cache
+;
+; res_http_media_cache is the HTTP backend for the core media cache. The
+; following options can be used to tune the behavior of the implementation
+; or left as default.
+;
+; See the module's and cURL's documentation for the exact meaning of these
+; options.
+
+
+[general]
+; Maximum time in seconds the transfer is allowed to complete in.
+;
+; See https://curl.se/libcurl/c/CURLOPT_TIMEOUT.html for details.
+;
+;timeout_secs = 180
+
+
+; The HTTP User-Agent to use for requests.
+;
+; See https://curl.se/libcurl/c/CURLOPT_USERAGENT.html for details.
+;
+;user_agent = asterisk-libcurl-agent/1.0
+
+
+; Follow HTTP 3xx redirects on requests. This can be combined with the
+; max_redirects option to limit the number of times a redirect will be
+; followed per request.
+;
+; See https://curl.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html for details.
+;
+;follow_location =  false
+
+
+; The maximum number of redirects to follow.
+;
+; See https://curl.se/libcurl/c/CURLOPT_MAXREDIRS.html for details.
+;
+;max_redirects = 8
+
+; The HTTP/HTTPS proxy to use for requests. Leave unspecified to not use
+; a proxy. This can be a URL with scheme, host and port.
+;
+; See https://curl.se/libcurl/c/CURLOPT_PROXY.html for details.
+;
+;proxy = https://localhost:1234
+
+
+; The life-time for DNS cache entries.
+;
+; See https://curl.se/libcurl/c/CURLOPT_DNS_CACHE_TIMEOUT.html for details.
+;
+;dns_cache_timeout_secs = 60
+
+
+; The comma separated list of allowed protocols for the request. Available with
+; cURL version 7.85.0 or later.
+; See https://curl.se/libcurl/c/CURLOPT_PROTOCOLS_STR.html for details.
+;
+;protocols = http,https
+
+; The comma separated list of allowed protocols for redirects. Available with
+; cURL version 7.85.0 or later. This can be used to prevent a redirect from
+; a protocol like HTTPS to another supported protocol of cURL.
+;
+; See https://curl.se/libcurl/c/CURLOPT_REDIR_PROTOCOLS_STR.html for details.
+;
+;redirect_protocols = http,https
diff --git a/configs/samples/voicemail.conf.sample b/configs/samples/voicemail.conf.sample
index 30054b59c7503c33f2fe83be8f2bf2f3f4868414..8771bea1e0a0dee65e77f8cfdd01cb05bd261646 100644
--- a/configs/samples/voicemail.conf.sample
+++ b/configs/samples/voicemail.conf.sample
@@ -4,10 +4,14 @@
 
 ; ********* NOTICE ************************************************************
 ;
-; NOTE: Asterisk has to edit this file to change a user's password.  This does
-; not currently work with the "#include <file>" directive for Asterisk
-; configuration files, nor when using realtime static configuration.
-; Do not use them with this configuration file.
+; NOTE: Asterisk has to edit this file to change a user's password, so
+; do not use realtime static configuration with this file.
+; To avoid requiring config updates on password updates, you could use the
+; passwordlocation option to store passwords separately.
+;
+; If your "source of truth" for voicemail passwords is elsewhere, you should
+; use the externpassnotify option to notify whatever stores the passwords
+; (e.g. external database) when there are changes, to keep it synchronized.
 ;
 ; NOTE: Mailboxes defined by app_voicemail MUST be referenced by the rest
 ; of the system as mailbox@context.  The rest of the system cannot add
@@ -262,6 +266,8 @@ pagerdateformat=%A, %B %d, %Y at %r
 			; option lets you customize the format sent to particular mailboxes.
 			; Useful if Windows users want wav49, but Linux users want gsm.
 			; [per-mailbox only]
+; attachextrecs=no	; Whether to attach recordings that are externally added to mailboxes,
+			; such as through MixMonitor. Default is no.
 ; saycid=yes 		; Say the caller id information before the message. If not described,
 			;     or set to no, it will be in the envelope. When enabled, if a recorded file
 			;     with the same name as the caller id exists in
diff --git a/configs/samples/vpb.conf.sample b/configs/samples/vpb.conf.sample
deleted file mode 100644
index bdc89dff5afea691952847c936759675e3ae66a4..0000000000000000000000000000000000000000
--- a/configs/samples/vpb.conf.sample
+++ /dev/null
@@ -1,248 +0,0 @@
-;
-; Voicetronix Voice Processing Board (VPB) telephony interface
-;
-; Configuration file
-;
-
-[general]
-;
-; Total number of Voicetronix cards in this machine
-;
-cards=0
-
-;
-; Which indication functions to use
-;    1 = use Asterisk functions
-;    0 = use VPB functions
-;
-indication=1
-
-;
-; Echo Canceller suppression threshold
-;    0    = no suppression threshold
-;    2048 = -18dB
-;    4096 = -24dB
-;
-;ecsuppthres=0
-
-;
-; Inter-digit delay timeout, used when collecting DTMF tones for dialling
-; from a station port.  Measured in milliseconds.
-;
-dtmfidd=3000
-
-;
-; How to play DTMF tones
-;    any value     = use Asterisk functions
-;    commented out = use VPB functions
-;
-;ast-dtmf=1
-
-;
-; How to detect DTMF tones
-;    any value     = use Asterisk functions
-;    commented out = use VPB functions
-;
-; NOTE: this setting is currently broken, and uncommenting it will
-; stop dialling from working.  Any volunteers to fix it?
-;ast-dtmf-det=1
-
-;
-; Use relaxed DTMF detection (ignored unless ast-dtmf-det is set)
-;
-relaxdtmf=1
-
-;
-; When we do a native bridge between two VPB channels:
-;    yes = only break the connection for '#' and '*'
-;    no  = break the connection for any DTMF
-;
-; NOTE: this is currently broken, and setting to no will segfault
-; Asterisk while dialling.  Any volunteers to fix it?
-;
-break-for-dtmf=yes
-
-;
-; The maximum period between received rings.  Measures in milliseconds.
-;
-timer_period_ring=4000
-
-
-[interfaces]
-;
-; Default language
-;
-language=en
-
-;
-; Default context
-;
-context=public
-
-;
-; Echo cancellation
-;     off  = no not use echo cancellation
-;     on   = use echo cancellation
-;
-echocancel=off
-
-;
-; Caller ID routines/signalling
-;   For FXO ports, select one of:
-;     on   = collect caller ID between 1st/2nd rings using VPB routines
-;     off  = do not use caller ID
-;     bell = bell202 as used in US, using Asterisk's caller ID routines
-;     v23  = v23 as used in the UK, using Asterisk's caller ID routines
-;   For FXS ports, set the channel's CID in '"name" <number>' format
-;
-; NOTE that other caller ID standards are supported in Asterisk, but are
-; not yet active in chan_vpb.  It should be reasonably trivial to add
-; support for the other standards (see the default chan_dahdi.conf for a
-; list of them) that Asterisk already handles.
-;
-callerid=bell
-
-;
-; Use a polarity reversal as the trigger for the start of caller ID,
-; rather than triggering after the first ring.
-;
-usepolaritycid=0
-
-;
-; Use loop drop to detect the end of a call.  On by default, but if you
-; experience unexpected hangups, try turning it off.
-;
-useloopdrop=1
-
-;
-; Use in-kernel bridging.  This will generally give lower delay audio if
-; bridging between two VPB channels.  It will not affect bridging
-; between VPB channels and other technologies.
-;
-usenativebridge=1
-
-;
-; Software transmit and receive gain.  Adjusting these will change the
-; volume of audio files that are played (tx) and recorded (rx).  It will
-; _not_ affect audio between channels in a native bridge.  It will,
-; however, affect the volume of audio between VPB channels and channels
-; using other technologies (such as VoIP channels).  Usually it's best to
-; leave these as they are.  If you're looking to get rid of echo, the
-; first thing to do is match your line impedance with the bal1/bal2/bal3
-; settings.
-;
-;txgain=0.0
-;rxgain=0.0
-
-;
-; Hardware transmit and receive gain.  Adjusting these will change the
-; volume of all audio on a channel.  The allowed range of settings is
-; -12.0 to 12.0 (measured in dB).
-;
-;txhwgain=0.0
-;rxhwgain=0.0
-
-;
-; Balance register settings, for matching the impedance of the card to
-; that of the connected equipment.  Only relevant for OpenLine and
-; OpenSwitch series cards.  Values should be in the range 0 - 255.
-;
-; We (Voicetronix) have determined the best codec balance values for
-; standard interfaces based on their US, Australian and European
-; specifications, shown below.
-;
-; US (600 ohm)
-;bal1=0xf8
-;bal2=0x1a
-;bal3=0x0c
-;
-; Australia (complex impedance)
-;bal1=0xf0
-;bal2=0x5d
-;bal3=0x79
-;
-; Europe (CTR-21)
-;bal1=0xf0
-;bal2=0x6e
-;bal3=0x75
-
-;
-; Logical groups can be assigned to allow outgoing rollover.  Groups range
-; from 0 to 63, and multiple groups can be specified.
-;
-group=1
-
-;
-; Ring groups (a.k.a. call groups) and pickup groups.  If a phone is
-; ringing and it is a member of a group which is one of your pickup
-; groups, then you can answer it by picking up and dialling *8#.  For
-; simple offices, just make these both the same.  Groups range from 0 to
-; 63.
-;
-callgroup=1
-pickupgroup=1
-
-;
-; If we haven't had a "grunt" (voice activity detection) for this many
-; seconds, then we hang up the line due to inactivity.  Default is one
-; hour.
-;
-grunttimeout=3600
-
-;
-; Type of line and line handling.  This setting will usually be overridden
-; on a per channel basis.  Valid settings are:
-;     fxo       = this is an FXO port
-;     immediate = this is an FXS port, with no dialtone or dialling
-;                   required (ie it is a "hotline")
-;     dialtone  = this is an FXS port, providing dialtone and dialling
-;
-mode=immediate
-
-; ------------------------------------------------------------------------
-; Channel definitions
-;
-; Each channel inherits the settings specified above, unless the are
-; overridden.  As a minimum, the board number and channel number must be
-; set, starting from 0 for the first board, and for the channels on each
-; board.  For example, board 0, channels 0 to 11, then board 1, channels
-; 0 to 11 for two OpenSwitch12 cards.
-;
-
-;
-; First board is an OpenSwitch12 card (jumpers at factory defaults)
-;
-;board=0
-;
-;mode=dialtone
-;context=from-handset
-;group=1
-;channel=0
-;channel=1
-;channel=2
-;channel=3
-;channel=4
-;channel=5
-;channel=6
-;channel=7
-;
-;mode=fxo
-;context=from-pstn
-;group=2
-;channel=8
-;channel=9
-;channel=10
-;channel=11
-
-;
-; Second board is an OpenLine4
-;
-;board=1
-;
-;mode=fxo
-;group=2
-;context=from-pstn
-;channel=0
-;channel=1
-;channel=2
-;channel=3
diff --git a/configure b/configure
index 22fe0acf792674b7ff6cf9b98a9209b8dc31a28e..4f763ae765aaa257551f69d2c880f8aef6250d1a 100755
--- a/configure
+++ b/configure
@@ -1,8 +1,8 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for asterisk trunk.
+# Generated by GNU Autoconf 2.69 for asterisk 20.
 #
-# Report bugs to <https://issues.asterisk.org>.
+# Report bugs to <https://github.com/asterisk/asterisk/issues>.
 #
 #
 # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
@@ -269,8 +269,8 @@ fi
     $as_echo "$0: be upgraded to zsh 4.3.4 or later."
   else
     $as_echo "$0: Please tell bug-autoconf@gnu.org and
-$0: https://issues.asterisk.org about your system,
-$0: including any error possibly output before this
+$0: https://github.com/asterisk/asterisk/issues about your
+$0: system, including any error possibly output before this
 $0: message. Then install a modern shell, or manually run
 $0: the script under such a shell if you do have one."
   fi
@@ -582,9 +582,9 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='asterisk'
 PACKAGE_TARNAME='asterisk'
-PACKAGE_VERSION='trunk'
-PACKAGE_STRING='asterisk trunk'
-PACKAGE_BUGREPORT='https://issues.asterisk.org'
+PACKAGE_VERSION='20'
+PACKAGE_STRING='asterisk 20'
+PACKAGE_BUGREPORT='https://github.com/asterisk/asterisk/issues'
 PACKAGE_URL=''
 
 ac_unique_file="main/asterisk.c"
@@ -652,7 +652,6 @@ PBX_LAUNCHD
 CONFIG_SDL
 PBX_SO_NOSIGPIPE
 PBX_MSG_NOSIGNAL
-PBX_IXJUSER
 GMIME_LIBS
 GMIME_CFLAGS
 PORTAUDIO_LIBS
@@ -670,8 +669,6 @@ NETSNMP_CFLAGS
 CONFIG_NEON29
 CONFIG_NEON
 CONFIG_MYSQLCLIENT
-PBX_MISDN_FAC_ERROR
-PBX_MISDN_FAC_RESULT
 ILBC_LIBS
 ILBC_CFLAGS
 ILBC_INTERNAL
@@ -687,6 +684,7 @@ AST_RPATH
 AST_NATIVE_ARCH
 AST_SHADOW_WARNINGS
 AST_NO_STRINGOP_TRUNCATION
+AST_NO_FORMAT_Y2K
 AST_NO_FORMAT_TRUNCATION
 AST_NO_STRICT_OVERFLOW
 AST_FORTIFY_SOURCE
@@ -707,6 +705,8 @@ LIBOBJS
 PERMANENT_DLOPEN
 DISABLE_XMLDOC
 CONFIG_LIBXML2
+LIBXML2_LIBS
+LIBXML2_CFLAGS
 JANSSON_LIBS
 JANSSON_CFLAGS
 UUID_LIB
@@ -722,10 +722,6 @@ PBX_X11
 X11_DIR
 X11_INCLUDE
 X11_LIB
-PBX_VPB
-VPB_DIR
-VPB_INCLUDE
-VPB_LIB
 PBX_VORBIS
 VORBIS_DIR
 VORBIS_INCLUDE
@@ -750,10 +746,6 @@ PBX_FREETDS
 FREETDS_DIR
 FREETDS_INCLUDE
 FREETDS_LIB
-PBX_SUPPSERV
-SUPPSERV_DIR
-SUPPSERV_INCLUDE
-SUPPSERV_LIB
 PBX_RT
 RT_DIR
 RT_INCLUDE
@@ -786,10 +778,6 @@ PBX_SQLITE3
 SQLITE3_DIR
 SQLITE3_INCLUDE
 SQLITE3_LIB
-PBX_SQLITE
-SQLITE_DIR
-SQLITE_INCLUDE
-SQLITE_LIB
 PBX_SPEEXDSP
 SPEEXDSP_DIR
 SPEEXDSP_INCLUDE
@@ -926,6 +914,14 @@ PBX_POPT
 POPT_DIR
 POPT_INCLUDE
 POPT_LIB
+PBX_PJSIP_EVSUB_PENDING_NOTIFY
+PJSIP_EVSUB_PENDING_NOTIFY_DIR
+PJSIP_EVSUB_PENDING_NOTIFY_INCLUDE
+PJSIP_EVSUB_PENDING_NOTIFY_LIB
+PBX_PJSIP_TLS_TRANSPORT_RESTART
+PJSIP_TLS_TRANSPORT_RESTART_DIR
+PJSIP_TLS_TRANSPORT_RESTART_INCLUDE
+PJSIP_TLS_TRANSPORT_RESTART_LIB
 PBX_PJSIP_OAUTH_AUTHENTICATION
 PJSIP_OAUTH_AUTHENTICATION_DIR
 PJSIP_OAUTH_AUTHENTICATION_INCLUDE
@@ -994,10 +990,6 @@ PBX_PGSQL
 PGSQL_DIR
 PGSQL_INCLUDE
 PGSQL_LIB
-PBX_OSS
-OSS_DIR
-OSS_INCLUDE
-OSS_LIB
 PBX_OSPTK
 OSPTK_DIR
 OSPTK_INCLUDE
@@ -1034,18 +1026,10 @@ PBX_NEON
 NEON_DIR
 NEON_INCLUDE
 NEON_LIB
-PBX_NBS
-NBS_DIR
-NBS_INCLUDE
-NBS_LIB
 PBX_MYSQLCLIENT
 MYSQLCLIENT_DIR
 MYSQLCLIENT_INCLUDE
 MYSQLCLIENT_LIB
-PBX_MISDN
-MISDN_DIR
-MISDN_INCLUDE
-MISDN_LIB
 LUA_VERSIONS
 PBX_LUA
 LUA_DIR
@@ -1092,10 +1076,6 @@ PBX_JACK
 JACK_DIR
 JACK_INCLUDE
 JACK_LIB
-PBX_ISDNNET
-ISDNNET_DIR
-ISDNNET_INCLUDE
-ISDNNET_LIB
 PBX_IODBC
 IODBC_DIR
 IODBC_INCLUDE
@@ -1406,7 +1386,6 @@ with_iksemel
 with_imap
 with_inotify
 with_iodbc
-with_isdnnet
 with_jack
 with_jansson
 with_uriparser
@@ -1417,9 +1396,7 @@ with_libedit
 with_libxml2
 with_libxslt
 with_lua
-with_misdn
 with_mysqlclient
-with_nbs
 with_neon
 with_neon29
 with_netsnmp
@@ -1429,7 +1406,6 @@ with_openr2
 with_opus
 with_opusfile
 with_osptk
-with_oss
 with_postgres
 with_beanstalk
 with_pjproject
@@ -1446,17 +1422,14 @@ with_spandsp
 with_ss7
 with_speex
 with_speexdsp
-with_sqlite
 with_sqlite3
 with_srtp
-with_suppserv
 with_tds
 with_timerfd
 with_tonezone
 with_unbound
 with_unixodbc
 with_vorbis
-with_vpb
 with_x11
 with_z
 enable_xmldoc
@@ -1491,6 +1464,8 @@ LIBEDIT_CFLAGS
 LIBEDIT_LIBS
 JANSSON_CFLAGS
 JANSSON_LIBS
+LIBXML2_CFLAGS
+LIBXML2_LIBS
 ILBC_CFLAGS
 ILBC_LIBS
 NETSNMP_CFLAGS
@@ -2057,7 +2032,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures asterisk trunk to adapt to many kinds of systems.
+\`configure' configures asterisk 20 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -2123,7 +2098,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of asterisk trunk:";;
+     short | recursive ) echo "Configuration of asterisk 20:";;
    esac
   cat <<\_ACEOF
 
@@ -2183,7 +2158,6 @@ Optional Packages:
   --with-imap=PATH        use UW IMAP Toolkit files in PATH
   --with-inotify=PATH     use inotify support files in PATH
   --with-iodbc=PATH       use iODBC files in PATH
-  --with-isdnnet=PATH     use ISDN4Linux files in PATH
   --with-jack=PATH        use Jack Audio Connection Kit files in PATH
   --with-jansson=PATH     use Jansson JSON library files in PATH
   --with-uriparser=PATH   use uriparser library files in PATH
@@ -2195,9 +2169,7 @@ Optional Packages:
   --with-libxml2=PATH     use LibXML2 files in PATH
   --with-libxslt=PATH     use LibXSLT files in PATH
   --with-lua=PATH         use Lua files in PATH
-  --with-misdn=PATH       use mISDN user files in PATH
   --with-mysqlclient=PATH use MySQL client files in PATH
-  --with-nbs=PATH         use Network Broadcast Sound files in PATH
   --with-neon=PATH        use neon files in PATH
   --with-neon29=PATH      use neon29 files in PATH
   --with-netsnmp=PATH     use Net-SNMP files in PATH
@@ -2207,7 +2179,6 @@ Optional Packages:
   --with-opus=PATH        use Opus files in PATH
   --with-opusfile=PATH    use Opusfile files in PATH
   --with-osptk=PATH       use OSP Toolkit files in PATH
-  --with-oss=PATH         use Open Sound System files in PATH
   --with-postgres=PATH    use PostgreSQL files in PATH
   --with-beanstalk=PATH   use Beanstalk Job Queue files in PATH
   --with-pjproject=PATH   use PJPROJECT files in PATH
@@ -2225,17 +2196,14 @@ Optional Packages:
   --with-speex=PATH       use Speex files in PATH
   --with-speex=PATH       use Speex preprocess routines files in PATH
   --with-speexdsp=PATH    use SpeexDSP files in PATH
-  --with-sqlite=PATH      use SQLite files in PATH
   --with-sqlite3=PATH     use SQLite files in PATH
   --with-srtp=PATH        use Secure RTP files in PATH
-  --with-suppserv=PATH    use mISDN Supplemental Services files in PATH
   --with-tds=PATH         use FreeTDS files in PATH
   --with-timerfd=PATH     use timerfd files in PATH
   --with-tonezone=PATH    use tonezone files in PATH
   --with-unbound=PATH     use unbound files in PATH
   --with-unixodbc=PATH    use unixODBC files in PATH
   --with-vorbis=PATH      use Vorbis files in PATH
-  --with-vpb=PATH         use Voicetronix API files in PATH
   --with-x11=PATH         use X11 files in PATH
   --with-z=PATH           use zlib compression files in PATH
 
@@ -2274,6 +2242,10 @@ Some influential environment variables:
               C compiler flags for JANSSON, overriding pkg-config
   JANSSON_LIBS
               linker flags for JANSSON, overriding pkg-config
+  LIBXML2_CFLAGS
+              C compiler flags for LIBXML2, overriding pkg-config
+  LIBXML2_LIBS
+              linker flags for LIBXML2, overriding pkg-config
   ILBC_CFLAGS C compiler flags for ILBC, overriding pkg-config
   ILBC_LIBS   linker flags for ILBC, overriding pkg-config
   NETSNMP_CFLAGS
@@ -2305,7 +2277,7 @@ Some influential environment variables:
 Use these variables to override the choices made by `configure' or to help
 it to find libraries and programs with nonstandard names/locations.
 
-Report bugs to <https://issues.asterisk.org>.
+Report bugs to <https://github.com/asterisk/asterisk/issues>.
 _ACEOF
 ac_status=$?
 fi
@@ -2368,7 +2340,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-asterisk configure trunk
+asterisk configure 20
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2529,9 +2501,9 @@ $as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
 $as_echo "$as_me: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&2;}
     { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
 $as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
-( $as_echo "## ------------------------------------------ ##
-## Report this to https://issues.asterisk.org ##
-## ------------------------------------------ ##"
+( $as_echo "## ---------------------------------------------------------- ##
+## Report this to https://github.com/asterisk/asterisk/issues ##
+## ---------------------------------------------------------- ##"
      ) | sed "s/^/$as_me: WARNING:     /" >&2
     ;;
 esac
@@ -3150,57 +3122,11 @@ rm -f conftest.val
   as_fn_set_status $ac_retval
 
 } # ac_fn_c_compute_int
-
-# ac_fn_cxx_try_link LINENO
-# -------------------------
-# Try to link conftest.$ac_ext, and return whether this succeeded.
-ac_fn_cxx_try_link ()
-{
-  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  rm -f conftest.$ac_objext conftest$ac_exeext
-  if { { ac_try="$ac_link"
-case "(($ac_try" in
-  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
-  *) ac_try_echo=$ac_try;;
-esac
-eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
-  (eval "$ac_link") 2>conftest.err
-  ac_status=$?
-  if test -s conftest.err; then
-    grep -v '^ *+' conftest.err >conftest.er1
-    cat conftest.er1 >&5
-    mv -f conftest.er1 conftest.err
-  fi
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; } && {
-	 test -z "$ac_cxx_werror_flag" ||
-	 test ! -s conftest.err
-       } && test -s conftest$ac_exeext && {
-	 test "$cross_compiling" = yes ||
-	 test -x conftest$ac_exeext
-       }; then :
-  ac_retval=0
-else
-  $as_echo "$as_me: failed program was:" >&5
-sed 's/^/| /' conftest.$ac_ext >&5
-
-	ac_retval=1
-fi
-  # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
-  # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
-  # interfere with the next link command; also delete a directory that is
-  # left behind by Apple's compiler.  We do this before executing the actions.
-  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
-  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
-  as_fn_set_status $ac_retval
-
-} # ac_fn_cxx_try_link
 cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by asterisk $as_me trunk, which was
+It was created by asterisk $as_me 20, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -4737,7 +4663,7 @@ $as_echo "$ac_cv_safe_to_define___extensions__" >&6; }
 
 # System default paths
 astsbindir='${sbindir}'
-astcachedir='/tmp'
+astcachedir='${localstatedir}/cache/asterisk'
 astetcdir='${sysconfdir}/asterisk'
 astheaderdir='${includedir}/asterisk'
 astlibdir='${libdir}'
@@ -4806,7 +4732,7 @@ $as_echo "#define _DARWIN_UNLIMITED_SELECT 1" >>confdefs.h
      ;;
      solaris*)
      if test ${prefix} = 'NONE'; then
-        astcachedir=/tmp
+        astcachedir=/var/cache/asterisk
         astetcdir=/var/etc/asterisk
         astsbindir=/opt/asterisk/sbin
         astlibdir=/opt/asterisk/lib
@@ -9403,7 +9329,7 @@ $as_echo "$as_me: checking OPENSSL with pkg-config" >&6;}
 	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether system openssl > 1.1.0" >&5
 $as_echo "$as_me: checking whether system openssl > 1.1.0" >&6;}
 
-   if test "x${PBX_OPENSSL}" != "x1" -a "${USE_OPENSSL}" != "no"; then
+      if test "x${PBX_OPENSSL}" != "x1" -a "${USE_OPENSSL}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for OPENSSL" >&5
@@ -9494,7 +9420,7 @@ fi
 		{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether alternate openssl11 is installed" >&5
 $as_echo "$as_me: checking whether alternate openssl11 is installed" >&6;}
 
-   if test "x${PBX_OPENSSL}" != "x1" -a "${USE_OPENSSL}" != "no"; then
+      if test "x${PBX_OPENSSL}" != "x1" -a "${USE_OPENSSL}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for OPENSSL" >&5
@@ -9590,7 +9516,7 @@ fi
 		{ $as_echo "$as_me:${as_lineno-$LINENO}: checking fallback system openssl" >&5
 $as_echo "$as_me: checking fallback system openssl" >&6;}
 
-   if test "x${PBX_OPENSSL}" != "x1" -a "${USE_OPENSSL}" != "no"; then
+      if test "x${PBX_OPENSSL}" != "x1" -a "${USE_OPENSSL}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for OPENSSL" >&5
@@ -10181,12 +10107,16 @@ $as_echo "configuring" >&6; }
 			y|ye|yes)
 			# Not to mention SSL is the default in PJProject and means "autodetect".
 			# In Asterisk, "./configure --with-ssl" means "must be present".
-			PJPROJECT_CONFIGURE_OPTS="${PJPROJECT_CONFIGURE_OPTS}"
+			PJPROJECT_CONFIGURE_OPTS="${PJPROJECT_CONFIGURE_OPTS} --with-ssl"
 			;;
 			*)
 			PJPROJECT_CONFIGURE_OPTS="${PJPROJECT_CONFIGURE_OPTS} --with-ssl=${with_ssl}"
 			;;
 			esac
+		else
+			if test $PBX_OPENSSL -eq 1 ; then
+				PJPROJECT_CONFIGURE_OPTS="${PJPROJECT_CONFIGURE_OPTS} --with-ssl"
+			fi
 		fi
 
 		# Determine if we're doing an out-of-tree build...
@@ -10279,6 +10209,12 @@ $as_echo "#define HAVE_PJSIP_OAUTH_AUTHENTICATION 1" >>confdefs.h
 $as_echo "#define HAVE_PJPROJECT_ON_VALID_ICE_PAIR_CALLBACK 1" >>confdefs.h
 
 
+$as_echo "#define HAVE_PJSIP_TLS_TRANSPORT_RESTART 1" >>confdefs.h
+
+
+$as_echo "#define HAVE_PJSIP_EVSUB_PENDING_NOTIFY 1" >>confdefs.h
+
+
 
 
 
@@ -10993,38 +10929,6 @@ fi
 
 
 
-    ISDNNET_DESCRIP="ISDN4Linux"
-    ISDNNET_OPTION="isdnnet"
-    PBX_ISDNNET=0
-
-# Check whether --with-isdnnet was given.
-if test "${with_isdnnet+set}" = set; then :
-  withval=$with_isdnnet;
-	case ${withval} in
-	n|no)
-	USE_ISDNNET=no
-	# -1 is a magic value used by menuselect to know that the package
-	# was disabled, other than 'not found'
-	PBX_ISDNNET=-1
-	;;
-	y|ye|yes)
-	ac_mandatory_list="${ac_mandatory_list} ISDNNET"
-	;;
-	*)
-	ISDNNET_DIR="${withval}"
-	ac_mandatory_list="${ac_mandatory_list} ISDNNET"
-	;;
-	esac
-
-fi
-
-
-
-
-
-
-
-
     JACK_DESCRIP="Jack Audio Connection Kit"
     JACK_OPTION="jack"
     PBX_JACK=0
@@ -11725,38 +11629,6 @@ fi
 
 
 
-    MISDN_DESCRIP="mISDN user"
-    MISDN_OPTION="misdn"
-    PBX_MISDN=0
-
-# Check whether --with-misdn was given.
-if test "${with_misdn+set}" = set; then :
-  withval=$with_misdn;
-	case ${withval} in
-	n|no)
-	USE_MISDN=no
-	# -1 is a magic value used by menuselect to know that the package
-	# was disabled, other than 'not found'
-	PBX_MISDN=-1
-	;;
-	y|ye|yes)
-	ac_mandatory_list="${ac_mandatory_list} MISDN"
-	;;
-	*)
-	MISDN_DIR="${withval}"
-	ac_mandatory_list="${ac_mandatory_list} MISDN"
-	;;
-	esac
-
-fi
-
-
-
-
-
-
-
-
     MYSQLCLIENT_DESCRIP="MySQL client"
     MYSQLCLIENT_OPTION="mysqlclient"
     PBX_MYSQLCLIENT=0
@@ -11789,38 +11661,6 @@ fi
 
 
 
-    NBS_DESCRIP="Network Broadcast Sound"
-    NBS_OPTION="nbs"
-    PBX_NBS=0
-
-# Check whether --with-nbs was given.
-if test "${with_nbs+set}" = set; then :
-  withval=$with_nbs;
-	case ${withval} in
-	n|no)
-	USE_NBS=no
-	# -1 is a magic value used by menuselect to know that the package
-	# was disabled, other than 'not found'
-	PBX_NBS=-1
-	;;
-	y|ye|yes)
-	ac_mandatory_list="${ac_mandatory_list} NBS"
-	;;
-	*)
-	NBS_DIR="${withval}"
-	ac_mandatory_list="${ac_mandatory_list} NBS"
-	;;
-	esac
-
-fi
-
-
-
-
-
-
-
-
     NEON_DESCRIP="neon"
     NEON_OPTION="neon"
     PBX_NEON=0
@@ -12109,38 +11949,6 @@ fi
 
 
 
-    OSS_DESCRIP="Open Sound System"
-    OSS_OPTION="oss"
-    PBX_OSS=0
-
-# Check whether --with-oss was given.
-if test "${with_oss+set}" = set; then :
-  withval=$with_oss;
-	case ${withval} in
-	n|no)
-	USE_OSS=no
-	# -1 is a magic value used by menuselect to know that the package
-	# was disabled, other than 'not found'
-	PBX_OSS=-1
-	;;
-	y|ye|yes)
-	ac_mandatory_list="${ac_mandatory_list} OSS"
-	;;
-	*)
-	OSS_DIR="${withval}"
-	ac_mandatory_list="${ac_mandatory_list} OSS"
-	;;
-	esac
-
-fi
-
-
-
-
-
-
-
-
     PGSQL_DESCRIP="PostgreSQL"
     PGSQL_OPTION="postgres"
     PBX_PGSQL=0
@@ -12418,6 +12226,30 @@ PBX_PJSIP_OAUTH_AUTHENTICATION=0
 
 
 
+
+PJSIP_TLS_TRANSPORT_RESTART_DESCRIP="PJSIP TLS Transport Restart Support"
+PJSIP_TLS_TRANSPORT_RESTART_OPTION=pjsip
+PJSIP_TLS_TRANSPORT_RESTART_DIR=${PJPROJECT_DIR}
+
+PBX_PJSIP_TLS_TRANSPORT_RESTART=0
+
+
+
+
+
+
+
+PJSIP_EVSUB_PENDING_NOTIFY_DESCRIP="PJSIP NOTIFY Required on SUBSCRIBE"
+PJSIP_EVSUB_PENDING_NOTIFY_OPTION=pjsip
+PJSIP_EVSUB_PENDING_NOTIFY_DIR=${PJPROJECT_DIR}
+
+PBX_PJSIP_EVSUB_PENDING_NOTIFY=0
+
+
+
+
+
+
 fi
 
 
@@ -13124,38 +12956,6 @@ PBX_SPEEX_PREPROCESS=0
 
 
 
-    SQLITE_DESCRIP="SQLite"
-    SQLITE_OPTION="sqlite"
-    PBX_SQLITE=0
-
-# Check whether --with-sqlite was given.
-if test "${with_sqlite+set}" = set; then :
-  withval=$with_sqlite;
-	case ${withval} in
-	n|no)
-	USE_SQLITE=no
-	# -1 is a magic value used by menuselect to know that the package
-	# was disabled, other than 'not found'
-	PBX_SQLITE=-1
-	;;
-	y|ye|yes)
-	ac_mandatory_list="${ac_mandatory_list} SQLITE"
-	;;
-	*)
-	SQLITE_DIR="${withval}"
-	ac_mandatory_list="${ac_mandatory_list} SQLITE"
-	;;
-	esac
-
-fi
-
-
-
-
-
-
-
-
     SQLITE3_DESCRIP="SQLite"
     SQLITE3_OPTION="sqlite3"
     PBX_SQLITE3=0
@@ -13292,38 +13092,6 @@ PBX_RT=0
 
 
 
-    SUPPSERV_DESCRIP="mISDN Supplemental Services"
-    SUPPSERV_OPTION="suppserv"
-    PBX_SUPPSERV=0
-
-# Check whether --with-suppserv was given.
-if test "${with_suppserv+set}" = set; then :
-  withval=$with_suppserv;
-	case ${withval} in
-	n|no)
-	USE_SUPPSERV=no
-	# -1 is a magic value used by menuselect to know that the package
-	# was disabled, other than 'not found'
-	PBX_SUPPSERV=-1
-	;;
-	y|ye|yes)
-	ac_mandatory_list="${ac_mandatory_list} SUPPSERV"
-	;;
-	*)
-	SUPPSERV_DIR="${withval}"
-	ac_mandatory_list="${ac_mandatory_list} SUPPSERV"
-	;;
-	esac
-
-fi
-
-
-
-
-
-
-
-
     FREETDS_DESCRIP="FreeTDS"
     FREETDS_OPTION="tds"
     PBX_FREETDS=0
@@ -13516,38 +13284,6 @@ fi
 
 
 
-    VPB_DESCRIP="Voicetronix API"
-    VPB_OPTION="vpb"
-    PBX_VPB=0
-
-# Check whether --with-vpb was given.
-if test "${with_vpb+set}" = set; then :
-  withval=$with_vpb;
-	case ${withval} in
-	n|no)
-	USE_VPB=no
-	# -1 is a magic value used by menuselect to know that the package
-	# was disabled, other than 'not found'
-	PBX_VPB=-1
-	;;
-	y|ye|yes)
-	ac_mandatory_list="${ac_mandatory_list} VPB"
-	;;
-	*)
-	VPB_DIR="${withval}"
-	ac_mandatory_list="${ac_mandatory_list} VPB"
-	;;
-	esac
-
-fi
-
-
-
-
-
-
-
-
     X11_DESCRIP="X11"
     X11_OPTION="x11"
     PBX_X11=0
@@ -14092,7 +13828,7 @@ done
 
 # Find required NetBSD Editline library (libedit).
 
-   if test "x${PBX_LIBEDIT}" != "x1" -a "${USE_LIBEDIT}" != "no"; then
+      if test "x${PBX_LIBEDIT}" != "x1" -a "${USE_LIBEDIT}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBEDIT" >&5
@@ -14552,7 +14288,7 @@ fi
 # Find required JSON support if bundled is not enabled.
 if test "$JANSSON_BUNDLED" = "no" ; then
 
-   if test "x${PBX_JANSSON}" != "x1" -a "${USE_JANSSON}" != "no"; then
+      if test "x${PBX_JANSSON}" != "x1" -a "${USE_JANSSON}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for JANSSON" >&5
@@ -14869,6 +14605,94 @@ fi
 
 
 
+      if test "x${PBX_LIBXML2}" != "x1" -a "${USE_LIBXML2}" != "no"; then
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBXML2" >&5
+$as_echo_n "checking for LIBXML2... " >&6; }
+
+if test -n "$LIBXML2_CFLAGS"; then
+    pkg_cv_LIBXML2_CFLAGS="$LIBXML2_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libxml-2.0\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libxml-2.0") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_LIBXML2_CFLAGS=`$PKG_CONFIG --cflags "libxml-2.0" 2>/dev/null`
+		      test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+if test -n "$LIBXML2_LIBS"; then
+    pkg_cv_LIBXML2_LIBS="$LIBXML2_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libxml-2.0\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libxml-2.0") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_LIBXML2_LIBS=`$PKG_CONFIG --libs "libxml-2.0" 2>/dev/null`
+		      test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi
+        if test $_pkg_short_errors_supported = yes; then
+	        LIBXML2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libxml-2.0" 2>&1`
+        else
+	        LIBXML2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libxml-2.0" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$LIBXML2_PKG_ERRORS" >&5
+
+
+            PBX_LIBXML2=0
+
+
+elif test $pkg_failed = untried; then
+     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+            PBX_LIBXML2=0
+
+
+else
+	LIBXML2_CFLAGS=$pkg_cv_LIBXML2_CFLAGS
+	LIBXML2_LIBS=$pkg_cv_LIBXML2_LIBS
+        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+            PBX_LIBXML2=1
+            LIBXML2_INCLUDE=$(echo ${LIBXML2_CFLAGS} | $SED -e "s|-std=c99||g")
+            LIBXML2_LIB="$LIBXML2_LIBS"
+
+$as_echo "#define HAVE_LIBXML2 1" >>confdefs.h
+
+
+fi
+   fi
+
+
 		if test "x${PBX_LIBXML2}" != "x1" -a "${USE_LIBXML2}" != "no"; then
 		PBX_LIBXML2=0
 		if test -n "$ac_tool_prefix"; then
@@ -15412,7 +15236,7 @@ else
     We can't simply define LARGE_OFF_T to be 9223372036854775807,
     since some C++ compilers masquerading as C compilers
     incorrectly reject 9223372036854775807.  */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
   int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
 		       && LARGE_OFF_T % 2147483647 == 1)
 		      ? 1 : -1];
@@ -15458,7 +15282,7 @@ else
     We can't simply define LARGE_OFF_T to be 9223372036854775807,
     since some C++ compilers masquerading as C compilers
     incorrectly reject 9223372036854775807.  */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
   int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
 		       && LARGE_OFF_T % 2147483647 == 1)
 		      ? 1 : -1];
@@ -15482,7 +15306,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
     We can't simply define LARGE_OFF_T to be 9223372036854775807,
     since some C++ compilers masquerading as C compilers
     incorrectly reject 9223372036854775807.  */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
   int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
 		       && LARGE_OFF_T % 2147483647 == 1)
 		      ? 1 : -1];
@@ -15527,7 +15351,7 @@ else
     We can't simply define LARGE_OFF_T to be 9223372036854775807,
     since some C++ compilers masquerading as C compilers
     incorrectly reject 9223372036854775807.  */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
   int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
 		       && LARGE_OFF_T % 2147483647 == 1)
 		      ? 1 : -1];
@@ -15551,7 +15375,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
     We can't simply define LARGE_OFF_T to be 9223372036854775807,
     since some C++ compilers masquerading as C compilers
     incorrectly reject 9223372036854775807.  */
-#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
+#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
   int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
 		       && LARGE_OFF_T % 2147483647 == 1)
 		      ? 1 : -1];
@@ -16851,6 +16675,8 @@ main ()
     if (*(data + i) != *(data3 + i))
       return 14;
   close (fd);
+  free (data);
+  free (data3);
   return 0;
 }
 _ACEOF
@@ -19339,6 +19165,19 @@ $as_echo "no" >&6; }
 fi
 
 
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for -Wno-format-y2k" >&5
+$as_echo_n "checking for -Wno-format-y2k... " >&6; }
+if $(${CC} -Wno-format-y2k -Werror -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+	AST_NO_FORMAT_Y2K=-Wno-format-y2k
+else
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+	AST_NO_FORMAT_Y2K=
+fi
+
+
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -Wno-stringop-truncation" >&5
 $as_echo_n "checking for -Wno-stringop-truncation... " >&6; }
 if $(${CC} -Wno-stringop-truncation -Werror -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then
@@ -19509,7 +19348,11 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 int
 main ()
 {
-int foo = res_ninit(NULL);
+
+				int foo;
+				foo = res_ninit(NULL);
+				foo = res_nsearch(NULL, NULL, 0, 0, NULL, 0);
+
   ;
   return 0;
 }
@@ -21274,7 +21117,7 @@ if test "${USE_ILBC}" != "no"; then
    fi
    if test "${ILBC_SYSTEM}" = "yes"; then
 
-   if test "x${PBX_ILBC}" != "x1" -a "${USE_ILBC}" != "no"; then
+      if test "x${PBX_ILBC}" != "x1" -a "${USE_ILBC}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ILBC" >&5
@@ -23056,409 +22899,6 @@ fi
 
 
 
-if test "x${PBX_MISDN}" != "x1" -a "${USE_MISDN}" != "no"; then
-   pbxlibdir=""
-   # if --with-MISDN=DIR has been specified, use it.
-   if test "x${MISDN_DIR}" != "x"; then
-      if test -d ${MISDN_DIR}/lib; then
-         pbxlibdir="-L${MISDN_DIR}/lib"
-      else
-         pbxlibdir="-L${MISDN_DIR}"
-      fi
-   fi
-
-      ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
-      CFLAGS="${CFLAGS} "
-      { $as_echo "$as_me:${as_lineno-$LINENO}: checking for mISDN_open in -lmISDN" >&5
-$as_echo_n "checking for mISDN_open in -lmISDN... " >&6; }
-if ${ac_cv_lib_mISDN_mISDN_open+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-lmISDN ${pbxlibdir}  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char mISDN_open ();
-int
-main ()
-{
-return mISDN_open ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
-  ac_cv_lib_mISDN_mISDN_open=yes
-else
-  ac_cv_lib_mISDN_mISDN_open=no
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_mISDN_mISDN_open" >&5
-$as_echo "$ac_cv_lib_mISDN_mISDN_open" >&6; }
-if test "x$ac_cv_lib_mISDN_mISDN_open" = xyes; then :
-  AST_MISDN_FOUND=yes
-else
-  AST_MISDN_FOUND=no
-fi
-
-      CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
-
-
-   # now check for the header.
-   if test "${AST_MISDN_FOUND}" = "yes"; then
-      MISDN_LIB="${pbxlibdir} -lmISDN "
-      # if --with-MISDN=DIR has been specified, use it.
-      if test "x${MISDN_DIR}" != "x"; then
-         MISDN_INCLUDE="-I${MISDN_DIR}/include"
-      fi
-      MISDN_INCLUDE="${MISDN_INCLUDE} "
-
-         # check for the header
-         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
-         CPPFLAGS="${CPPFLAGS} ${MISDN_INCLUDE}"
-         ac_fn_c_check_header_mongrel "$LINENO" "mISDNuser/mISDNlib.h" "ac_cv_header_mISDNuser_mISDNlib_h" "$ac_includes_default"
-if test "x$ac_cv_header_mISDNuser_mISDNlib_h" = xyes; then :
-  MISDN_HEADER_FOUND=1
-else
-  MISDN_HEADER_FOUND=0
-fi
-
-
-         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
-
-      if test "x${MISDN_HEADER_FOUND}" = "x0" ; then
-         MISDN_LIB=""
-         MISDN_INCLUDE=""
-      else
-
-         PBX_MISDN=1
-         cat >>confdefs.h <<_ACEOF
-#define HAVE_MISDN 1
-_ACEOF
-
-      fi
-   fi
-fi
-
-
-
-if test "${PBX_MISDN}" = 1; then
-
-if test "x${PBX_ISDNNET}" != "x1" -a "${USE_ISDNNET}" != "no"; then
-   pbxlibdir=""
-   # if --with-ISDNNET=DIR has been specified, use it.
-   if test "x${ISDNNET_DIR}" != "x"; then
-      if test -d ${ISDNNET_DIR}/lib; then
-         pbxlibdir="-L${ISDNNET_DIR}/lib"
-      else
-         pbxlibdir="-L${ISDNNET_DIR}"
-      fi
-   fi
-
-      ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
-      CFLAGS="${CFLAGS} "
-      { $as_echo "$as_me:${as_lineno-$LINENO}: checking for init_manager in -lisdnnet" >&5
-$as_echo_n "checking for init_manager in -lisdnnet... " >&6; }
-if ${ac_cv_lib_isdnnet_init_manager+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-lisdnnet ${pbxlibdir} -lmISDN -lpthread $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char init_manager ();
-int
-main ()
-{
-return init_manager ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
-  ac_cv_lib_isdnnet_init_manager=yes
-else
-  ac_cv_lib_isdnnet_init_manager=no
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_isdnnet_init_manager" >&5
-$as_echo "$ac_cv_lib_isdnnet_init_manager" >&6; }
-if test "x$ac_cv_lib_isdnnet_init_manager" = xyes; then :
-  AST_ISDNNET_FOUND=yes
-else
-  AST_ISDNNET_FOUND=no
-fi
-
-      CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
-
-
-   # now check for the header.
-   if test "${AST_ISDNNET_FOUND}" = "yes"; then
-      ISDNNET_LIB="${pbxlibdir} -lisdnnet -lmISDN -lpthread"
-      # if --with-ISDNNET=DIR has been specified, use it.
-      if test "x${ISDNNET_DIR}" != "x"; then
-         ISDNNET_INCLUDE="-I${ISDNNET_DIR}/include"
-      fi
-      ISDNNET_INCLUDE="${ISDNNET_INCLUDE} "
-
-         # check for the header
-         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
-         CPPFLAGS="${CPPFLAGS} ${ISDNNET_INCLUDE}"
-         ac_fn_c_check_header_mongrel "$LINENO" "mISDNuser/isdn_net.h" "ac_cv_header_mISDNuser_isdn_net_h" "$ac_includes_default"
-if test "x$ac_cv_header_mISDNuser_isdn_net_h" = xyes; then :
-  ISDNNET_HEADER_FOUND=1
-else
-  ISDNNET_HEADER_FOUND=0
-fi
-
-
-         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
-
-      if test "x${ISDNNET_HEADER_FOUND}" = "x0" ; then
-         ISDNNET_LIB=""
-         ISDNNET_INCLUDE=""
-      else
-
-         PBX_ISDNNET=1
-         cat >>confdefs.h <<_ACEOF
-#define HAVE_ISDNNET 1
-_ACEOF
-
-      fi
-   fi
-fi
-
-
-
-if test "x${PBX_SUPPSERV}" != "x1" -a "${USE_SUPPSERV}" != "no"; then
-   pbxlibdir=""
-   # if --with-SUPPSERV=DIR has been specified, use it.
-   if test "x${SUPPSERV_DIR}" != "x"; then
-      if test -d ${SUPPSERV_DIR}/lib; then
-         pbxlibdir="-L${SUPPSERV_DIR}/lib"
-      else
-         pbxlibdir="-L${SUPPSERV_DIR}"
-      fi
-   fi
-
-      ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
-      CFLAGS="${CFLAGS} "
-      { $as_echo "$as_me:${as_lineno-$LINENO}: checking for encodeFac in -lsuppserv" >&5
-$as_echo_n "checking for encodeFac in -lsuppserv... " >&6; }
-if ${ac_cv_lib_suppserv_encodeFac+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-lsuppserv ${pbxlibdir}  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char encodeFac ();
-int
-main ()
-{
-return encodeFac ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
-  ac_cv_lib_suppserv_encodeFac=yes
-else
-  ac_cv_lib_suppserv_encodeFac=no
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_suppserv_encodeFac" >&5
-$as_echo "$ac_cv_lib_suppserv_encodeFac" >&6; }
-if test "x$ac_cv_lib_suppserv_encodeFac" = xyes; then :
-  AST_SUPPSERV_FOUND=yes
-else
-  AST_SUPPSERV_FOUND=no
-fi
-
-      CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
-
-
-   # now check for the header.
-   if test "${AST_SUPPSERV_FOUND}" = "yes"; then
-      SUPPSERV_LIB="${pbxlibdir} -lsuppserv "
-      # if --with-SUPPSERV=DIR has been specified, use it.
-      if test "x${SUPPSERV_DIR}" != "x"; then
-         SUPPSERV_INCLUDE="-I${SUPPSERV_DIR}/include"
-      fi
-      SUPPSERV_INCLUDE="${SUPPSERV_INCLUDE} "
-
-         # check for the header
-         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
-         CPPFLAGS="${CPPFLAGS} ${SUPPSERV_INCLUDE}"
-         ac_fn_c_check_header_mongrel "$LINENO" "mISDNuser/suppserv.h" "ac_cv_header_mISDNuser_suppserv_h" "$ac_includes_default"
-if test "x$ac_cv_header_mISDNuser_suppserv_h" = xyes; then :
-  SUPPSERV_HEADER_FOUND=1
-else
-  SUPPSERV_HEADER_FOUND=0
-fi
-
-
-         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
-
-      if test "x${SUPPSERV_HEADER_FOUND}" = "x0" ; then
-         SUPPSERV_LIB=""
-         SUPPSERV_INCLUDE=""
-      else
-
-         PBX_SUPPSERV=1
-         cat >>confdefs.h <<_ACEOF
-#define HAVE_SUPPSERV 1
-_ACEOF
-
-      fi
-   fi
-fi
-
-
-
-    if test "x${PBX_MISDN_FAC_RESULT}" != "x1"; then
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for Fac_RESULT in mISDNuser/suppserv.h" >&5
-$as_echo_n "checking for Fac_RESULT in mISDNuser/suppserv.h... " >&6; }
-	saved_cppflags="${CPPFLAGS}"
-	if test "x${MISDN_FAC_RESULT_DIR}" != "x"; then
-	    MISDN_FAC_RESULT_INCLUDE="-I${MISDN_FAC_RESULT_DIR}/include"
-	fi
-	CPPFLAGS="${CPPFLAGS} ${MISDN_FAC_RESULT_INCLUDE}"
-
-	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
- #include <mISDNuser/suppserv.h>
-int
-main ()
-{
-#if defined(Fac_RESULT)
-				int foo = 0;
-			        #else
-			        int foo = bar;
-			        #endif
-				0
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-     { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-		PBX_MISDN_FAC_RESULT=1
-
-$as_echo "#define HAVE_MISDN_FAC_RESULT 1" >>confdefs.h
-
-
-
-else
-     { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-	CPPFLAGS="${saved_cppflags}"
-    fi
-
-
-
-    if test "x${PBX_MISDN_FAC_ERROR}" != "x1"; then
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for Fac_ERROR in mISDNuser/suppserv.h" >&5
-$as_echo_n "checking for Fac_ERROR in mISDNuser/suppserv.h... " >&6; }
-	saved_cppflags="${CPPFLAGS}"
-	if test "x${MISDN_FAC_ERROR_DIR}" != "x"; then
-	    MISDN_FAC_ERROR_INCLUDE="-I${MISDN_FAC_ERROR_DIR}/include"
-	fi
-	CPPFLAGS="${CPPFLAGS} ${MISDN_FAC_ERROR_INCLUDE}"
-
-	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
- #include <mISDNuser/suppserv.h>
-int
-main ()
-{
-#if defined(Fac_ERROR)
-				int foo = 0;
-			        #else
-			        int foo = bar;
-			        #endif
-				0
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-     { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-		PBX_MISDN_FAC_ERROR=1
-
-$as_echo "#define HAVE_MISDN_FAC_ERROR 1" >>confdefs.h
-
-
-
-else
-     { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-	CPPFLAGS="${saved_cppflags}"
-    fi
-
-
-   ac_fn_c_check_header_mongrel "$LINENO" "linux/mISDNdsp.h" "ac_cv_header_linux_mISDNdsp_h" "$ac_includes_default"
-if test "x$ac_cv_header_linux_mISDNdsp_h" = xyes; then :
-
-cat >>confdefs.h <<_ACEOF
-#define MISDN_1_2 1
-_ACEOF
-
-fi
-
-
-   ac_fn_c_check_member "$LINENO" "Q931_info_t" "redirect_dn" "ac_cv_member_Q931_info_t_redirect_dn" "#include <mISDNuser/mISDNlib.h>
-"
-if test "x$ac_cv_member_Q931_info_t_redirect_dn" = xyes; then :
-
-else
-  PBX_MISDN=0
-fi
-
-fi
-
-
-
 		if test "x${PBX_MYSQLCLIENT}" != "x1" -a "${USE_MYSQLCLIENT}" != "no"; then
 		PBX_MYSQLCLIENT=0
 		if test -n "$ac_tool_prefix"; then
@@ -23634,103 +23074,6 @@ rm -f core conftest.err conftest.$ac_objext \
 fi
 
 
-if test "x${PBX_NBS}" != "x1" -a "${USE_NBS}" != "no"; then
-   pbxlibdir=""
-   # if --with-NBS=DIR has been specified, use it.
-   if test "x${NBS_DIR}" != "x"; then
-      if test -d ${NBS_DIR}/lib; then
-         pbxlibdir="-L${NBS_DIR}/lib"
-      else
-         pbxlibdir="-L${NBS_DIR}"
-      fi
-   fi
-
-      ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
-      CFLAGS="${CFLAGS} "
-      { $as_echo "$as_me:${as_lineno-$LINENO}: checking for nbs_connect in -lnbs" >&5
-$as_echo_n "checking for nbs_connect in -lnbs... " >&6; }
-if ${ac_cv_lib_nbs_nbs_connect+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-lnbs ${pbxlibdir}  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char nbs_connect ();
-int
-main ()
-{
-return nbs_connect ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
-  ac_cv_lib_nbs_nbs_connect=yes
-else
-  ac_cv_lib_nbs_nbs_connect=no
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nbs_nbs_connect" >&5
-$as_echo "$ac_cv_lib_nbs_nbs_connect" >&6; }
-if test "x$ac_cv_lib_nbs_nbs_connect" = xyes; then :
-  AST_NBS_FOUND=yes
-else
-  AST_NBS_FOUND=no
-fi
-
-      CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
-
-
-   # now check for the header.
-   if test "${AST_NBS_FOUND}" = "yes"; then
-      NBS_LIB="${pbxlibdir} -lnbs "
-      # if --with-NBS=DIR has been specified, use it.
-      if test "x${NBS_DIR}" != "x"; then
-         NBS_INCLUDE="-I${NBS_DIR}/include"
-      fi
-      NBS_INCLUDE="${NBS_INCLUDE} "
-
-         # check for the header
-         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
-         CPPFLAGS="${CPPFLAGS} ${NBS_INCLUDE}"
-         ac_fn_c_check_header_mongrel "$LINENO" "nbs.h" "ac_cv_header_nbs_h" "$ac_includes_default"
-if test "x$ac_cv_header_nbs_h" = xyes; then :
-  NBS_HEADER_FOUND=1
-else
-  NBS_HEADER_FOUND=0
-fi
-
-
-         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
-
-      if test "x${NBS_HEADER_FOUND}" = "x0" ; then
-         NBS_LIB=""
-         NBS_INCLUDE=""
-      else
-
-         PBX_NBS=1
-         cat >>confdefs.h <<_ACEOF
-#define HAVE_NBS 1
-_ACEOF
-
-      fi
-   fi
-fi
-
-
-
-
 		if test "x${PBX_NEON}" != "x1" -a "${USE_NEON}" != "no"; then
 		PBX_NEON=0
 		if test -n "$ac_tool_prefix"; then
@@ -23995,7 +23338,7 @@ rm -f core conftest.err conftest.$ac_objext \
 
 
 
-   if test "x${PBX_NETSNMP}" != "x1" -a "${USE_NETSNMP}" != "no"; then
+      if test "x${PBX_NETSNMP}" != "x1" -a "${USE_NETSNMP}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for NETSNMP" >&5
@@ -25061,216 +24404,6 @@ fi
 
 
 
-# possible places for oss definitions
-
-if test "x${PBX_OSS}" != "x1" -a "${USE_OSS}" != "no"; then
-   pbxlibdir=""
-   # if --with-OSS=DIR has been specified, use it.
-   if test "x${OSS_DIR}" != "x"; then
-      if test -d ${OSS_DIR}/lib; then
-         pbxlibdir="-L${OSS_DIR}/lib"
-      else
-         pbxlibdir="-L${OSS_DIR}"
-      fi
-   fi
-
-      # empty lib, assume only headers
-      AST_OSS_FOUND=yes
-
-
-   # now check for the header.
-   if test "${AST_OSS_FOUND}" = "yes"; then
-      OSS_LIB="${pbxlibdir} -lossaudio "
-      # if --with-OSS=DIR has been specified, use it.
-      if test "x${OSS_DIR}" != "x"; then
-         OSS_INCLUDE="-I${OSS_DIR}/include"
-      fi
-      OSS_INCLUDE="${OSS_INCLUDE} "
-
-         # check for the header
-         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
-         CPPFLAGS="${CPPFLAGS} ${OSS_INCLUDE}"
-         ac_fn_c_check_header_mongrel "$LINENO" "linux/soundcard.h" "ac_cv_header_linux_soundcard_h" "$ac_includes_default"
-if test "x$ac_cv_header_linux_soundcard_h" = xyes; then :
-  OSS_HEADER_FOUND=1
-else
-  OSS_HEADER_FOUND=0
-fi
-
-
-         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
-
-      if test "x${OSS_HEADER_FOUND}" = "x0" ; then
-         OSS_LIB=""
-         OSS_INCLUDE=""
-      else
-
-            # only checking headers -> no library
-            OSS_LIB=""
-
-         PBX_OSS=1
-         cat >>confdefs.h <<_ACEOF
-#define HAVE_OSS 1
-_ACEOF
-
-      fi
-   fi
-fi
-
-
-
-if test "x${PBX_OSS}" != "x1" -a "${USE_OSS}" != "no"; then
-   pbxlibdir=""
-   # if --with-OSS=DIR has been specified, use it.
-   if test "x${OSS_DIR}" != "x"; then
-      if test -d ${OSS_DIR}/lib; then
-         pbxlibdir="-L${OSS_DIR}/lib"
-      else
-         pbxlibdir="-L${OSS_DIR}"
-      fi
-   fi
-
-      # empty lib, assume only headers
-      AST_OSS_FOUND=yes
-
-
-   # now check for the header.
-   if test "${AST_OSS_FOUND}" = "yes"; then
-      OSS_LIB="${pbxlibdir} -lossaudio "
-      # if --with-OSS=DIR has been specified, use it.
-      if test "x${OSS_DIR}" != "x"; then
-         OSS_INCLUDE="-I${OSS_DIR}/include"
-      fi
-      OSS_INCLUDE="${OSS_INCLUDE} "
-
-         # check for the header
-         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
-         CPPFLAGS="${CPPFLAGS} ${OSS_INCLUDE}"
-         ac_fn_c_check_header_mongrel "$LINENO" "sys/soundcard.h" "ac_cv_header_sys_soundcard_h" "$ac_includes_default"
-if test "x$ac_cv_header_sys_soundcard_h" = xyes; then :
-  OSS_HEADER_FOUND=1
-else
-  OSS_HEADER_FOUND=0
-fi
-
-
-         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
-
-      if test "x${OSS_HEADER_FOUND}" = "x0" ; then
-         OSS_LIB=""
-         OSS_INCLUDE=""
-      else
-
-            # only checking headers -> no library
-            OSS_LIB=""
-
-         PBX_OSS=1
-         cat >>confdefs.h <<_ACEOF
-#define HAVE_OSS 1
-_ACEOF
-
-      fi
-   fi
-fi
-
-
-
-if test "x${PBX_OSS}" != "x1" -a "${USE_OSS}" != "no"; then
-   pbxlibdir=""
-   # if --with-OSS=DIR has been specified, use it.
-   if test "x${OSS_DIR}" != "x"; then
-      if test -d ${OSS_DIR}/lib; then
-         pbxlibdir="-L${OSS_DIR}/lib"
-      else
-         pbxlibdir="-L${OSS_DIR}"
-      fi
-   fi
-
-      ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
-      CFLAGS="${CFLAGS} "
-      { $as_echo "$as_me:${as_lineno-$LINENO}: checking for oss_ioctl_mixer in -lossaudio" >&5
-$as_echo_n "checking for oss_ioctl_mixer in -lossaudio... " >&6; }
-if ${ac_cv_lib_ossaudio_oss_ioctl_mixer+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-lossaudio ${pbxlibdir}  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char oss_ioctl_mixer ();
-int
-main ()
-{
-return oss_ioctl_mixer ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
-  ac_cv_lib_ossaudio_oss_ioctl_mixer=yes
-else
-  ac_cv_lib_ossaudio_oss_ioctl_mixer=no
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ossaudio_oss_ioctl_mixer" >&5
-$as_echo "$ac_cv_lib_ossaudio_oss_ioctl_mixer" >&6; }
-if test "x$ac_cv_lib_ossaudio_oss_ioctl_mixer" = xyes; then :
-  AST_OSS_FOUND=yes
-else
-  AST_OSS_FOUND=no
-fi
-
-      CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
-
-
-   # now check for the header.
-   if test "${AST_OSS_FOUND}" = "yes"; then
-      OSS_LIB="${pbxlibdir} -lossaudio "
-      # if --with-OSS=DIR has been specified, use it.
-      if test "x${OSS_DIR}" != "x"; then
-         OSS_INCLUDE="-I${OSS_DIR}/include"
-      fi
-      OSS_INCLUDE="${OSS_INCLUDE} "
-
-         # check for the header
-         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
-         CPPFLAGS="${CPPFLAGS} ${OSS_INCLUDE}"
-         ac_fn_c_check_header_mongrel "$LINENO" "soundcard.h" "ac_cv_header_soundcard_h" "$ac_includes_default"
-if test "x$ac_cv_header_soundcard_h" = xyes; then :
-  OSS_HEADER_FOUND=1
-else
-  OSS_HEADER_FOUND=0
-fi
-
-
-         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
-
-      if test "x${OSS_HEADER_FOUND}" = "x0" ; then
-         OSS_LIB=""
-         OSS_INCLUDE=""
-      else
-
-         PBX_OSS=1
-         cat >>confdefs.h <<_ACEOF
-#define HAVE_OSS 1
-_ACEOF
-
-      fi
-   fi
-fi
-
-
-
 PG_CONFIG=":"
 if test "${USE_PGSQL}" != "no"; then
    if test "x${PGSQL_DIR}" != "x"; then
@@ -25603,7 +24736,7 @@ fi
 if test "$USE_PJPROJECT" != "no" ; then
    if test "$PJPROJECT_BUNDLED" = "no" ; then
 
-   if test "x${PBX_PJPROJECT}" != "x1" -a "${USE_PJPROJECT}" != "no"; then
+      if test "x${PBX_PJPROJECT}" != "x1" -a "${USE_PJPROJECT}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PJPROJECT" >&5
@@ -26848,6 +25981,116 @@ _ACEOF
 fi
 
 
+
+if test "x${PBX_PJSIP_TLS_TRANSPORT_RESTART}" != "x1" -a "${USE_PJSIP_TLS_TRANSPORT_RESTART}" != "no"; then
+   pbxlibdir=""
+   # if --with-PJSIP_TLS_TRANSPORT_RESTART=DIR has been specified, use it.
+   if test "x${PJSIP_TLS_TRANSPORT_RESTART_DIR}" != "x"; then
+      if test -d ${PJSIP_TLS_TRANSPORT_RESTART_DIR}/lib; then
+         pbxlibdir="-L${PJSIP_TLS_TRANSPORT_RESTART_DIR}/lib"
+      else
+         pbxlibdir="-L${PJSIP_TLS_TRANSPORT_RESTART_DIR}"
+      fi
+   fi
+
+      ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
+      CFLAGS="${CFLAGS} $PJPROJECT_CFLAGS"
+      { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pjsip_tls_transport_restart in -lpjsip" >&5
+$as_echo_n "checking for pjsip_tls_transport_restart in -lpjsip... " >&6; }
+if ${ac_cv_lib_pjsip_pjsip_tls_transport_restart+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIB $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char pjsip_tls_transport_restart ();
+int
+main ()
+{
+return pjsip_tls_transport_restart ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_pjsip_pjsip_tls_transport_restart=yes
+else
+  ac_cv_lib_pjsip_pjsip_tls_transport_restart=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pjsip_pjsip_tls_transport_restart" >&5
+$as_echo "$ac_cv_lib_pjsip_pjsip_tls_transport_restart" >&6; }
+if test "x$ac_cv_lib_pjsip_pjsip_tls_transport_restart" = xyes; then :
+  AST_PJSIP_TLS_TRANSPORT_RESTART_FOUND=yes
+else
+  AST_PJSIP_TLS_TRANSPORT_RESTART_FOUND=no
+fi
+
+      CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
+
+
+   # now check for the header.
+   if test "${AST_PJSIP_TLS_TRANSPORT_RESTART_FOUND}" = "yes"; then
+      PJSIP_TLS_TRANSPORT_RESTART_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIB"
+      # if --with-PJSIP_TLS_TRANSPORT_RESTART=DIR has been specified, use it.
+      if test "x${PJSIP_TLS_TRANSPORT_RESTART_DIR}" != "x"; then
+         PJSIP_TLS_TRANSPORT_RESTART_INCLUDE="-I${PJSIP_TLS_TRANSPORT_RESTART_DIR}/include"
+      fi
+      PJSIP_TLS_TRANSPORT_RESTART_INCLUDE="${PJSIP_TLS_TRANSPORT_RESTART_INCLUDE} $PJPROJECT_CFLAGS"
+
+         # check for the header
+         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
+         CPPFLAGS="${CPPFLAGS} ${PJSIP_TLS_TRANSPORT_RESTART_INCLUDE}"
+         ac_fn_c_check_header_mongrel "$LINENO" "pjsip.h" "ac_cv_header_pjsip_h" "$ac_includes_default"
+if test "x$ac_cv_header_pjsip_h" = xyes; then :
+  PJSIP_TLS_TRANSPORT_RESTART_HEADER_FOUND=1
+else
+  PJSIP_TLS_TRANSPORT_RESTART_HEADER_FOUND=0
+fi
+
+
+         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
+
+      if test "x${PJSIP_TLS_TRANSPORT_RESTART_HEADER_FOUND}" = "x0" ; then
+         PJSIP_TLS_TRANSPORT_RESTART_LIB=""
+         PJSIP_TLS_TRANSPORT_RESTART_INCLUDE=""
+      else
+
+         PBX_PJSIP_TLS_TRANSPORT_RESTART=1
+         cat >>confdefs.h <<_ACEOF
+#define HAVE_PJSIP_TLS_TRANSPORT_RESTART 1
+_ACEOF
+
+      fi
+   fi
+fi
+
+
+
+      	 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pending_notify in evsub.c" >&5
+		   $as_echo_n "checking for pending_notify in evsub... " >&6; }
+		 pending_notify=$(${SED} -n -r -e '/^struct\s+pjsip_evsub/,/^\s+void\s+*mod_data/!d ; /pending_notify/p' $(find $PJSIP_EVSUB_PENDING_NOTIFY_DIR -name evsub.c))
+		 if test -n "$pending_notify" ; then
+
+$as_echo "#define HAVE_PJSIP_EVSUB_PENDING_NOTIFY 1" >>confdefs.h
+
+			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+			  $as_echo "yes" >&6; }
+		 else
+			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+			  $as_echo "no" >&6; }
+		 fi
       fi
    fi
 
@@ -26856,7 +26099,7 @@ fi
 
 
 
-   if test "x${PBX_PYTHONDEV}" != "x1" -a "${USE_PYTHONDEV}" != "no"; then
+      if test "x${PBX_PYTHONDEV}" != "x1" -a "${USE_PYTHONDEV}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PYTHONDEV" >&5
@@ -26944,7 +26187,7 @@ fi
    fi
 
 
-   if test "x${PBX_PYTHONDEV}" != "x1" -a "${USE_PYTHONDEV}" != "no"; then
+      if test "x${PBX_PYTHONDEV}" != "x1" -a "${USE_PYTHONDEV}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PYTHONDEV" >&5
@@ -27032,7 +26275,7 @@ fi
    fi
 
 
-   if test "x${PBX_PYTHONDEV}" != "x1" -a "${USE_PYTHONDEV}" != "no"; then
+      if test "x${PBX_PYTHONDEV}" != "x1" -a "${USE_PYTHONDEV}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PYTHONDEV" >&5
@@ -27228,7 +26471,7 @@ fi
 
 
 
-   if test "x${PBX_PORTAUDIO}" != "x1" -a "${USE_PORTAUDIO}" != "no"; then
+      if test "x${PBX_PORTAUDIO}" != "x1" -a "${USE_PORTAUDIO}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PORTAUDIO" >&5
@@ -31476,103 +30719,6 @@ fi
 
 
 
-if test "x${PBX_SQLITE}" != "x1" -a "${USE_SQLITE}" != "no"; then
-   pbxlibdir=""
-   # if --with-SQLITE=DIR has been specified, use it.
-   if test "x${SQLITE_DIR}" != "x"; then
-      if test -d ${SQLITE_DIR}/lib; then
-         pbxlibdir="-L${SQLITE_DIR}/lib"
-      else
-         pbxlibdir="-L${SQLITE_DIR}"
-      fi
-   fi
-
-      ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
-      CFLAGS="${CFLAGS} "
-      { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqlite_exec in -lsqlite" >&5
-$as_echo_n "checking for sqlite_exec in -lsqlite... " >&6; }
-if ${ac_cv_lib_sqlite_sqlite_exec+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-lsqlite ${pbxlibdir}  $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char sqlite_exec ();
-int
-main ()
-{
-return sqlite_exec ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
-  ac_cv_lib_sqlite_sqlite_exec=yes
-else
-  ac_cv_lib_sqlite_sqlite_exec=no
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sqlite_sqlite_exec" >&5
-$as_echo "$ac_cv_lib_sqlite_sqlite_exec" >&6; }
-if test "x$ac_cv_lib_sqlite_sqlite_exec" = xyes; then :
-  AST_SQLITE_FOUND=yes
-else
-  AST_SQLITE_FOUND=no
-fi
-
-      CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
-
-
-   # now check for the header.
-   if test "${AST_SQLITE_FOUND}" = "yes"; then
-      SQLITE_LIB="${pbxlibdir} -lsqlite "
-      # if --with-SQLITE=DIR has been specified, use it.
-      if test "x${SQLITE_DIR}" != "x"; then
-         SQLITE_INCLUDE="-I${SQLITE_DIR}/include"
-      fi
-      SQLITE_INCLUDE="${SQLITE_INCLUDE} "
-
-         # check for the header
-         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
-         CPPFLAGS="${CPPFLAGS} ${SQLITE_INCLUDE}"
-         ac_fn_c_check_header_mongrel "$LINENO" "sqlite.h" "ac_cv_header_sqlite_h" "$ac_includes_default"
-if test "x$ac_cv_header_sqlite_h" = xyes; then :
-  SQLITE_HEADER_FOUND=1
-else
-  SQLITE_HEADER_FOUND=0
-fi
-
-
-         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
-
-      if test "x${SQLITE_HEADER_FOUND}" = "x0" ; then
-         SQLITE_LIB=""
-         SQLITE_INCLUDE=""
-      else
-
-         PBX_SQLITE=1
-         cat >>confdefs.h <<_ACEOF
-#define HAVE_SQLITE 1
-_ACEOF
-
-      fi
-   fi
-fi
-
-
-
-
 if test "x${PBX_SQLITE3}" != "x1" -a "${USE_SQLITE3}" != "no"; then
    pbxlibdir=""
    # if --with-SQLITE3=DIR has been specified, use it.
@@ -33239,7 +32385,7 @@ fi
 
 for ver in 3.0 2.6 2.4 2.2 2.0; do
 
-   if test "x${PBX_GMIME}" != "x1" -a "${USE_GMIME}" != "no"; then
+      if test "x${PBX_GMIME}" != "x1" -a "${USE_GMIME}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GMIME" >&5
@@ -33799,79 +32945,6 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
     fi
 
 
-ac_ext=cpp
-ac_cpp='$CXXCPP $CPPFLAGS'
-ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
-ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
-ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
-
-
-if test "${USE_VPB}" != "no"; then
-   { $as_echo "$as_me:${as_lineno-$LINENO}: checking for vpb_open in -lvpb" >&5
-$as_echo_n "checking for vpb_open in -lvpb... " >&6; }
-   saved_libs="${LIBS}"
-   saved_cppflags="${CPPFLAGS}"
-   if test "x${VPB_DIR}" != "x"; then
-      if test -d ${VPB_DIR}/lib; then
-         vpblibdir=${VPB_DIR}/lib
-      else
-         vpblibdir=${VPB_DIR}
-      fi
-      LIBS="${LIBS} -L${vpblibdir}"
-      CPPFLAGS="${CPPFLAGS} -I${VPB_DIR}/include"
-   fi
-   LIBS="${PTHREAD_LIBS} ${LIBS} -lvpb"
-   CPPFLAGS="${CPPFLAGS} ${PTHREAD_CFLAGS}"
-
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-	#include <vpbapi.h>
-int
-main ()
-{
-int q = vpb_open(0,0);
-  ;
-  return 0;
-}
-
-_ACEOF
-if ac_fn_cxx_try_link "$LINENO"; then :
-  	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-		ac_cv_lib_vpb_vpb_open="yes"
-
-else
-  	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-		ac_cv_lib_vpb_vpb_open="no"
-
-
-fi
-rm -f core conftest.err conftest.$ac_objext \
-    conftest$ac_exeext conftest.$ac_ext
-   LIBS="${saved_libs}"
-   CPPFLAGS="${saved_cppflags}"
-   if test "${ac_cv_lib_vpb_vpb_open}" = "yes"; then
-	VPB_LIB="-lvpb"
-	if test "${VPB_DIR}" != ""; then
-	   VPB_LIB="-L${vpblibdir}  ${VPB_LIB}"
-	   VPB_INCLUDE="-I${VPB_DIR}/include"
-	fi
-	PBX_VPB=1
-
-$as_echo "#define HAVE_VPB 1" >>confdefs.h
-
-   fi
-fi
-
-ac_ext=c
-ac_cpp='$CPP $CPPFLAGS'
-ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
-ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
-ac_compiler_gnu=$ac_cv_c_compiler_gnu
-
-
 
 if test "x${PBX_ZLIB}" != "x1" -a "${USE_ZLIB}" != "no"; then
    pbxlibdir=""
@@ -34011,22 +33084,6 @@ fi
 
 
 
-ac_fn_c_check_header_compile "$LINENO" "linux/ixjuser.h" "ac_cv_header_linux_ixjuser_h" "
-				   #include <linux/version.h>
-				   #ifdef HAVE_LINUX_COMPILER_H
-				   #include <linux/compiler.h>
-				   #endif
-
-"
-if test "x$ac_cv_header_linux_ixjuser_h" = xyes; then :
-  PBX_IXJUSER=1
-else
-  PBX_IXJUSER=0
-fi
-
-
-
-
 # Used in res/res_pktccops
 
     if test "x${PBX_MSG_NOSIGNAL}" != "x1"; then
@@ -34676,7 +33733,7 @@ fi
 
 
 
-   if test "x${PBX_GTK2}" != "x1" -a "${USE_GTK2}" != "no"; then
+      if test "x${PBX_GTK2}" != "x1" -a "${USE_GTK2}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GTK2" >&5
@@ -34787,7 +33844,7 @@ fi
 
 
 
-   if test "x${PBX_SYSTEMD}" != "x1" -a "${USE_SYSTEMD}" != "no"; then
+      if test "x${PBX_SYSTEMD}" != "x1" -a "${USE_SYSTEMD}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SYSTEMD" >&5
@@ -35969,7 +35026,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by asterisk $as_me trunk, which was
+This file was extended by asterisk $as_me 20, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -36025,13 +35082,13 @@ $config_files
 Configuration headers:
 $config_headers
 
-Report bugs to <https://issues.asterisk.org>."
+Report bugs to <https://github.com/asterisk/asterisk/issues>."
 
 _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-asterisk config.status trunk
+asterisk config.status 20
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff --git a/configure.ac b/configure.ac
index 0d6817e68a2976fbb22add10e127eac2abbe2a0b..d3ded2799858991fe4f99b1d95dcb4a6c051134a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
 AC_PREREQ(2.60a)
 
-AC_INIT([asterisk], [trunk], [https://issues.asterisk.org])
+AC_INIT([asterisk], [20], [https://github.com/asterisk/asterisk/issues])
 
 # cross-compile macros
 AC_CANONICAL_BUILD
@@ -34,7 +34,7 @@ AC_USE_SYSTEM_EXTENSIONS	dnl note- does not work on FreeBSD
 
 # System default paths
 AC_SUBST([astsbindir],        ['${sbindir}'])dnl
-AC_SUBST([astcachedir],       ['/tmp'])dnl
+AC_SUBST([astcachedir],       ['${localstatedir}/cache/asterisk'])dnl
 AC_SUBST([astetcdir],         ['${sysconfdir}/asterisk'])dnl
 AC_SUBST([astheaderdir],      ['${includedir}/asterisk'])dnl
 AC_SUBST([astlibdir],         ['${libdir}'])dnl
@@ -99,7 +99,7 @@ case "${host_os}" in
      ;;
      solaris*)
      if test ${prefix} = 'NONE'; then
-        astcachedir=/tmp
+        astcachedir=/var/cache/asterisk
         astetcdir=/var/etc/asterisk
         astsbindir=/opt/asterisk/sbin
         astlibdir=/opt/asterisk/lib
@@ -546,7 +546,6 @@ AST_EXT_LIB_SETUP([IKSEMEL], [Iksemel Jabber], [iksemel])
 AST_EXT_LIB_SETUP([IMAP_TK], [UW IMAP Toolkit], [imap])
 AST_EXT_LIB_SETUP([INOTIFY], [inotify support], [inotify])
 AST_EXT_LIB_SETUP([IODBC], [iODBC], [iodbc])
-AST_EXT_LIB_SETUP([ISDNNET], [ISDN4Linux], [isdnnet])
 AST_EXT_LIB_SETUP([JACK], [Jack Audio Connection Kit], [jack])
 AST_EXT_LIB_SETUP([JANSSON], [Jansson JSON library], [jansson])
 AST_EXT_LIB_SETUP([URIPARSER], [uriparser library], [uriparser])
@@ -560,9 +559,7 @@ AST_EXT_LIB_SETUP([LIBXSLT], [LibXSLT], [libxslt])
 AST_EXT_LIB_SETUP_OPTIONAL([LIBXSLT_CLEANUP], [LibXSLT Library Cleanup Function], [LIBXSLT], [libxslt])
 AST_EXT_LIB_SETUP([LUA], [Lua], [lua])
 AC_ARG_VAR([LUA_VERSIONS],[A space separated list of target lua versions to test.])
-AST_EXT_LIB_SETUP([MISDN], [mISDN user], [misdn])
 AST_EXT_LIB_SETUP([MYSQLCLIENT], [MySQL client], [mysqlclient])
-AST_EXT_LIB_SETUP([NBS], [Network Broadcast Sound], [nbs])
 AST_EXT_LIB_SETUP([NEON], [neon], [neon])
 AST_EXT_LIB_SETUP([NEON29], [neon29], [neon29])
 AST_EXT_LIB_SETUP([NETSNMP], [Net-SNMP], [netsnmp])
@@ -572,7 +569,6 @@ AST_EXT_LIB_SETUP([OPENR2], [MFR2], [openr2])
 AST_EXT_LIB_SETUP([OPUS], [Opus], [opus])
 AST_EXT_LIB_SETUP([OPUSFILE], [Opusfile], [opusfile])
 AST_EXT_LIB_SETUP([OSPTK], [OSP Toolkit], [osptk])
-AST_EXT_LIB_SETUP([OSS], [Open Sound System], [oss])
 AST_EXT_LIB_SETUP([PGSQL], [PostgreSQL], [postgres])
 AST_EXT_LIB_SETUP([BEANSTALK], [Beanstalk Job Queue], [beanstalk])
 
@@ -593,6 +589,8 @@ AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_INV_ACCEPT_MULTIPLE_SDP_ANSWERS], [PJSIP INVIT
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_ENDPOINT_COMPACT_FORM], [PJSIP Compact Form Support on Endpoint], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE], [PJSIP Transport Connection Reuse Disabling], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_OAUTH_AUTHENTICATION], [PJSIP OAuth Authentication Support], [PJPROJECT], [pjsip])
+AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TLS_TRANSPORT_RESTART], [PJSIP TLS Transport Restart Support], [PJPROJECT], [pjsip])
+AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_EVSUB_PENDING_NOTIFY], [PJSIP NOTIFY Required on SUBSCRIBE], [PJPROJECT], [pjsip])
 fi
 
 AST_EXT_LIB_SETUP([POPT], [popt], [popt])
@@ -634,7 +632,6 @@ AST_EXT_LIB_SETUP([SPEEX], [Speex], [speex])
 AST_EXT_LIB_SETUP([SPEEX_PREPROCESS], [Speex preprocess routines], [speex])
 AST_EXT_LIB_SETUP([SPEEXDSP], [SpeexDSP], [speexdsp])
 AST_EXT_LIB_SETUP_DEPENDENT([SPEEX_PREPROCESS], [speex_preprocess_ctl], [], [speex])
-AST_EXT_LIB_SETUP([SQLITE], [SQLite], [sqlite])
 AST_EXT_LIB_SETUP([SQLITE3], [SQLite], [sqlite3])
 AST_EXT_LIB_SETUP([SRTP], [Secure RTP], [srtp])
 AST_EXT_LIB_SETUP_OPTIONAL([SRTP_256], [SRTP Library AES-256 (ICM)], [SRTP], [srtp])
@@ -643,14 +640,12 @@ AST_EXT_LIB_SETUP_OPTIONAL([SRTP_GCM], [SRTP Library AES-128 (GCM) and AES-256 (
 AST_EXT_LIB_SETUP_OPTIONAL([SRTP_SHUTDOWN], [SRTP Library Shutdown Function], [SRTP], [srtp])
 AST_EXT_LIB_SETUP_OPTIONAL([SRTP_GET_VERSION], [SRTP Library Version Function], [SRTP], [srtp])
 AST_EXT_LIB_SETUP_OPTIONAL([RT], [Realtime functions], [rt])
-AST_EXT_LIB_SETUP([SUPPSERV], [mISDN Supplemental Services], [suppserv])
 AST_EXT_LIB_SETUP([FREETDS], [FreeTDS], [tds])
 AST_EXT_LIB_SETUP([TIMERFD], [timerfd], [timerfd])
 AST_EXT_LIB_SETUP([TONEZONE], [tonezone], [tonezone])
 AST_EXT_LIB_SETUP([UNBOUND], [unbound], [unbound])
 AST_EXT_LIB_SETUP([UNIXODBC], [unixODBC], [unixodbc])
 AST_EXT_LIB_SETUP([VORBIS], [Vorbis], [vorbis])
-AST_EXT_LIB_SETUP([VPB], [Voicetronix API], [vpb])
 AST_EXT_LIB_SETUP([X11], [X11], [x11])
 AST_EXT_LIB_SETUP([ZLIB], [zlib compression], [z])
 
@@ -744,6 +739,7 @@ fi
 # See if clock_gettime is in librt
 AST_EXT_LIB_CHECK([RT], [rt], [clock_gettime], [])
 
+AST_PKG_CONFIG_CHECK([LIBXML2], [libxml-2.0])
 AST_EXT_TOOL_CHECK([LIBXML2], [xml2-config], , ,
         [#include <libxml/tree.h>
         #include <libxml/parser.h>],
@@ -1403,6 +1399,16 @@ else
 fi
 AC_SUBST(AST_NO_FORMAT_TRUNCATION)
 
+AC_MSG_CHECKING(for -Wno-format-y2k)
+if $(${CC} -Wno-format-y2k -Werror -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then
+	AC_MSG_RESULT(yes)
+	AST_NO_FORMAT_Y2K=-Wno-format-y2k
+else
+	AC_MSG_RESULT(no)
+	AST_NO_FORMAT_Y2K=
+fi
+AC_SUBST(AST_NO_FORMAT_Y2K)
+
 AC_MSG_CHECKING(for -Wno-stringop-truncation)
 if $(${CC} -Wno-stringop-truncation -Werror -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then
 	AC_MSG_RESULT(yes)
@@ -1481,7 +1487,11 @@ AC_LINK_IFELSE(
 			#include <arpa/nameser.h>
 			#endif
 			#include <resolv.h>],
-			[int foo = res_ninit(NULL);])],
+			[
+				int foo;
+				foo = res_ninit(NULL);
+				foo = res_nsearch(NULL, NULL, 0, 0, NULL, 0);
+			])],
 	AC_MSG_RESULT(yes)
 	AC_DEFINE([HAVE_RES_NINIT], 1, [Define to 1 if your system has the re-entrant resolver functions.])
 	AC_SEARCH_LIBS(res_9_ndestroy, resolv)
@@ -2324,18 +2334,6 @@ AC_CHECK_FUNCS([kevent64])
 
 AST_EXT_LIB_CHECK([LDAP], [ldap], [ldap_initialize], [ldap.h])
 
-AST_EXT_LIB_CHECK([MISDN], [mISDN], [mISDN_open], [mISDNuser/mISDNlib.h])
-
-if test "${PBX_MISDN}" = 1; then
-   AST_EXT_LIB_CHECK([ISDNNET], [isdnnet], [init_manager], [mISDNuser/isdn_net.h], [-lmISDN -lpthread])
-   AST_EXT_LIB_CHECK([SUPPSERV], [suppserv], [encodeFac], [mISDNuser/suppserv.h])
-   AST_C_DEFINE_CHECK([MISDN_FAC_RESULT], [Fac_RESULT], [mISDNuser/suppserv.h])
-   AST_C_DEFINE_CHECK([MISDN_FAC_ERROR], [Fac_ERROR], [mISDNuser/suppserv.h])
-   AC_CHECK_HEADER([linux/mISDNdsp.h], [AC_DEFINE_UNQUOTED([MISDN_1_2], 1, [Build chan_misdn for mISDN 1.2 or later.])])
-   AC_CHECK_MEMBER([Q931_info_t.redirect_dn], [], [PBX_MISDN=0], [#include <mISDNuser/mISDNlib.h>])
-fi
-
-
 AST_EXT_TOOL_CHECK([MYSQLCLIENT], [mysql_config])
 
 if test "${PBX_MYSQLCLIENT}" = 1; then
@@ -2357,8 +2355,6 @@ if test "${PBX_MYSQLCLIENT}" = 1; then
    )
 fi
 
-AST_EXT_LIB_CHECK([NBS], [nbs], [nbs_connect], [nbs.h])
-
 AST_EXT_TOOL_CHECK([NEON], [neon-config])
 
 AST_EXT_TOOL_CHECK([NEON29], [neon-config], , [--libs],
@@ -2401,11 +2397,6 @@ AST_EXT_LIB_CHECK([BLUETOOTH], [bluetooth], [ba2str], [bluetooth/bluetooth.h])
 
 AST_EXT_LIB_CHECK([BEANSTALK], [beanstalk], [bs_version], [beanstalk.h])
 
-# possible places for oss definitions
-AST_EXT_LIB_CHECK([OSS], [ossaudio], [], [linux/soundcard.h])
-AST_EXT_LIB_CHECK([OSS], [ossaudio], [], [sys/soundcard.h])
-AST_EXT_LIB_CHECK([OSS], [ossaudio], [oss_ioctl_mixer], [soundcard.h])
-
 PG_CONFIG=":"
 if test "${USE_PGSQL}" != "no"; then
    if test "x${PGSQL_DIR}" != "x"; then
@@ -2520,6 +2511,19 @@ if test "$USE_PJPROJECT" != "no" ; then
          AST_EXT_LIB_CHECK([PJSIP_INV_SESSION_REF], [pjsip], [pjsip_inv_add_ref], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
          AST_EXT_LIB_CHECK([PJSIP_AUTH_CLT_DEINIT], [pjsip], [pjsip_auth_clt_deinit], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
          AST_EXT_LIB_CHECK([PJSIP_TSX_LAYER_FIND_TSX2], [pjsip], [pjsip_tsx_layer_find_tsx2], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
+         AST_EXT_LIB_CHECK([PJSIP_TLS_TRANSPORT_RESTART], [pjsip], [pjsip_tls_transport_restart], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
+
+      	 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pending_notify in evsub.c" >&5
+		   $as_echo_n "checking for pending_notify in evsub... " >&6; }
+		 pending_notify=$(${SED} -n -r -e '/^struct\s+pjsip_evsub/,/^\s+void\s+*mod_data/!d ; /pending_notify/p' $(find $PJSIP_EVSUB_PENDING_NOTIFY_DIR -name evsub.c))
+		 if test -n "$pending_notify" ; then
+		 	AC_DEFINE(HAVE_PJSIP_EVSUB_PENDING_NOTIFY, 1, [Define to 1 if evsub requires a NOTIFY on SUBSCRIBE.])
+			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+			  $as_echo "yes" >&6; }
+		 else
+			{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+			  $as_echo "no" >&6; }
+		 fi
       fi
    fi
 
@@ -2666,8 +2670,6 @@ fi
 
 AC_SUBST(PBX_SPEEX_PREPROCESS)
 
-AST_EXT_LIB_CHECK([SQLITE], [sqlite], [sqlite_exec], [sqlite.h])
-
 AST_EXT_LIB_CHECK([SQLITE3], [sqlite3], [sqlite3_open], [sqlite3.h], [${PTHREAD_LIBS}], [${PTHREAD_CFLAGS}])
 
 if test "${PBX_SQLITE3}" != 1; then
@@ -2788,51 +2790,6 @@ AST_EXT_LIB_CHECK([TONEZONE], [tonezone], [tone_zone_find], [dahdi/tonezone.h],
 AST_EXT_LIB_CHECK([VORBIS], [vorbis], [vorbis_info_init], [vorbis/codec.h], [-lm -lvorbisenc -lvorbisfile])
 AST_C_DECLARE_CHECK([VORBIS_OPEN_CALLBACKS], [OV_CALLBACKS_NOCLOSE], [vorbis/vorbisfile.h])
 
-AC_LANG_PUSH(C++)
-
-if test "${USE_VPB}" != "no"; then
-   AC_MSG_CHECKING(for vpb_open in -lvpb)
-   saved_libs="${LIBS}"
-   saved_cppflags="${CPPFLAGS}"
-   if test "x${VPB_DIR}" != "x"; then
-      if test -d ${VPB_DIR}/lib; then
-         vpblibdir=${VPB_DIR}/lib
-      else
-         vpblibdir=${VPB_DIR}
-      fi
-      LIBS="${LIBS} -L${vpblibdir}"
-      CPPFLAGS="${CPPFLAGS} -I${VPB_DIR}/include"
-   fi
-   LIBS="${PTHREAD_LIBS} ${LIBS} -lvpb"
-   CPPFLAGS="${CPPFLAGS} ${PTHREAD_CFLAGS}"
-   AC_LINK_IFELSE(
-	[
-	AC_LANG_PROGRAM(
-	[#include <vpbapi.h>],
-	[int q = vpb_open(0,0);])
-	],
-	[	AC_MSG_RESULT(yes)
-		ac_cv_lib_vpb_vpb_open="yes"
-	],
-	[	AC_MSG_RESULT(no)
-		ac_cv_lib_vpb_vpb_open="no"
-	]
-	)
-   LIBS="${saved_libs}"
-   CPPFLAGS="${saved_cppflags}"
-   if test "${ac_cv_lib_vpb_vpb_open}" = "yes"; then
-	VPB_LIB="-lvpb"
-	if test "${VPB_DIR}" != ""; then
-	   VPB_LIB="-L${vpblibdir}  ${VPB_LIB}"
-	   VPB_INCLUDE="-I${VPB_DIR}/include"
-	fi
-	PBX_VPB=1
-	AC_DEFINE([HAVE_VPB], 1, [Define if your system has the VoiceTronix API libraries.])
-   fi
-fi
-
-AC_LANG_POP
-
 AST_EXT_LIB_CHECK([ZLIB], [z], [compress], [zlib.h])
 
 if test "x${PBX_UNIXODBC}" = "x1" -o "x${PBX_IODBC}" = "x1"; then
@@ -2852,14 +2809,6 @@ fi
 AC_CHECK_HEADER([linux/compiler.h],
                 [AC_DEFINE_UNQUOTED([HAVE_LINUX_COMPILER_H], 1, [Define to 1 if your system has linux/compiler.h.])])
 
-AC_CHECK_HEADER([linux/ixjuser.h], [PBX_IXJUSER=1], [PBX_IXJUSER=0], [
-				   #include <linux/version.h>
-				   #ifdef HAVE_LINUX_COMPILER_H
-				   #include <linux/compiler.h>
-				   #endif
-				   ])
-AC_SUBST(PBX_IXJUSER)
-
 # Used in res/res_pktccops
 AST_C_DEFINE_CHECK([MSG_NOSIGNAL], [MSG_NOSIGNAL], [sys/socket.h])
 AST_C_DEFINE_CHECK([SO_NOSIGPIPE], [SO_NOSIGPIPE], [sys/socket.h])
diff --git a/contrib/ast-db-manage/config/versions/0bee61aa9425_allow_180_ringing_with_sdp.py b/contrib/ast-db-manage/config/versions/0bee61aa9425_allow_180_ringing_with_sdp.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6711406e23c344244131aaeaa1ab4ee8b90d288
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/0bee61aa9425_allow_180_ringing_with_sdp.py
@@ -0,0 +1,36 @@
+"""allow_sending_180_after_183
+
+Revision ID: 0bee61aa9425
+Revises: 8f72185e437f
+Create Date: 2022-04-07 13:51:33.400664
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+# revision identifiers, used by Alembic.
+revision = '0bee61aa9425'
+down_revision = '8f72185e437f'
+AST_BOOL_NAME = 'ast_bool_values'
+# We'll just ignore the n/y and f/t abbreviations as Asterisk does not write
+# those aliases.
+AST_BOOL_VALUES = [ '0', '1',
+                    'off', 'on',
+                    'false', 'true',
+                    'no', 'yes' ]
+
+def upgrade():
+    ############################# Enums ##############################
+
+    # ast_bool_values has already been created, so use postgres enum object
+    # type to get around "already created" issue - works okay with mysql
+    ast_bool_values = ENUM(*AST_BOOL_VALUES, name=AST_BOOL_NAME, create_type=False)
+
+    op.add_column('ps_globals', sa.Column('allow_sending_180_after_183', ast_bool_values))
+
+
+def downgrade():
+    if op.get_context().bind.dialect.name == 'mssql':
+        op.drop_constraint('ck_ps_globals_allow_sending_180_after_183_ast_bool_values', 'ps_globals')
+    op.drop_column('ps_globals', 'allow_sending_180_after_183')
diff --git a/contrib/ast-db-manage/config/versions/18e0805d367f_max_random_initial_delay.py b/contrib/ast-db-manage/config/versions/18e0805d367f_max_random_initial_delay.py
new file mode 100644
index 0000000000000000000000000000000000000000..343b841463b4f3c6d4b8b9aa17e358c3452b7813
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/18e0805d367f_max_random_initial_delay.py
@@ -0,0 +1,21 @@
+"""max_random_initial_delay
+
+Revision ID: 18e0805d367f
+Revises: 0bee61aa9425
+Create Date: 2022-05-18 17:07:02.626045
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '18e0805d367f'
+down_revision = '0bee61aa9425'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    op.add_column('ps_registrations', sa.Column('max_random_initial_delay', sa.Integer))
+
+def downgrade():
+    op.drop_column('ps_registrations', 'max_random_initial_delay')
diff --git a/contrib/ast-db-manage/config/versions/417c0247fd7e_add_security_negotiation_and_security_.py b/contrib/ast-db-manage/config/versions/417c0247fd7e_add_security_negotiation_and_security_.py
new file mode 100644
index 0000000000000000000000000000000000000000..34847fcb560e5beeba4ce3708cdcc8a055a42057
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/417c0247fd7e_add_security_negotiation_and_security_.py
@@ -0,0 +1,49 @@
+"""add security_negotiation and security_mechanisms to endpoint
+
+Revision ID: 417c0247fd7e
+Revises: 539f68bede2c
+Create Date: 2022-08-08 15:35:31.416964
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '417c0247fd7e'
+down_revision = '539f68bede2c'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+SECURITY_NEGOTIATION_NAME = 'security_negotiation_values'
+SECURITY_NEGOTIATION_VALUES = ['no', 'mediasec']
+
+def upgrade():
+    context = op.get_context()
+
+    if context.bind.dialect.name == 'postgresql':
+        security_negotiation_values = ENUM(*SECURITY_NEGOTIATION_VALUES, name=SECURITY_NEGOTIATION_NAME)
+        security_negotiation_values.create(op.get_bind(), checkfirst=False)
+
+    op.add_column('ps_endpoints', sa.Column('security_negotiation',
+        ENUM(*SECURITY_NEGOTIATION_VALUES, name=SECURITY_NEGOTIATION_NAME, create_type=False)))
+    op.add_column('ps_endpoints', sa.Column('security_mechanisms', sa.String(512)))
+
+    op.add_column('ps_registrations', sa.Column('security_negotiation',
+        ENUM(*SECURITY_NEGOTIATION_VALUES, name=SECURITY_NEGOTIATION_NAME, create_type=False)))
+    op.add_column('ps_registrations', sa.Column('security_mechanisms', sa.String(512)))
+
+def downgrade():
+    context = op.get_context()
+
+    if context.bind.dialect.name == 'mssql':
+        op.drop_constraint('ck_ps_endpoints_security_negotiation_security_negotiation_values', 'ps_endpoints')
+        op.drop_constraint('ck_ps_registrations_security_negotiation_security_negotiation_values', 'ps_registrations')
+
+    op.drop_column('ps_endpoints', 'security_negotiation')
+    op.drop_column('ps_endpoints', 'security_mechanisms')
+    op.drop_column('ps_registrations', 'security_negotiation')
+    op.drop_column('ps_registrations', 'security_mechanisms')
+
+    if context.bind.dialect.name == 'postgresql':
+        enum = ENUM(*SECURITY_NEGOTIATION_VALUES, name=SECURITY_NEGOTIATION_NAME)
+        enum.drop(op.get_bind(), checkfirst=False)
\ No newline at end of file
diff --git a/contrib/ast-db-manage/config/versions/539f68bede2c_add_peer_supported_to_100rel.py b/contrib/ast-db-manage/config/versions/539f68bede2c_add_peer_supported_to_100rel.py
new file mode 100644
index 0000000000000000000000000000000000000000..6293dabb0f03e25db4a91786f88cbfe609805c7e
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/539f68bede2c_add_peer_supported_to_100rel.py
@@ -0,0 +1,57 @@
+"""Add peer_supported to 100rel
+
+Revision ID: 539f68bede2c
+Revises: 9f3692b1654b
+Create Date: 2022-08-10 09:36:16.576049
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '539f68bede2c'
+down_revision = '9f3692b1654b'
+
+from alembic import op
+from sqlalchemy.dialects.postgresql import ENUM
+import sqlalchemy as sa
+
+
+OLD_ENUM = ['no', 'required', 'yes']
+NEW_ENUM = ['no', 'required', 'peer_supported', 'yes']
+
+old_type = sa.Enum(*OLD_ENUM, name='pjsip_100rel_values')
+new_type = sa.Enum(*NEW_ENUM, name='pjsip_100rel_values_v2')
+
+def upgrade():
+    context = op.get_context()
+
+    # Upgrading to this revision WILL clear your directmedia values.
+    if context.bind.dialect.name != 'postgresql':
+        op.alter_column('ps_endpoints', '100rel',
+                        type_=new_type,
+                        existing_type=old_type)
+    else:
+        enum = ENUM(*NEW_ENUM, name='pjsip_100rel_values_v2')
+        enum.create(op.get_bind(), checkfirst=False)
+
+        op.execute('ALTER TABLE ps_endpoints ALTER COLUMN 100rel TYPE'
+                   ' pjsip_100rel_values_v2 USING'
+                   ' 100rel::text::pjsip_100rel_values_v2')
+
+        ENUM(name="pjsip_100rel_values").drop(op.get_bind(), checkfirst=False)
+
+def downgrade():
+    context = op.get_context()
+
+    if context.bind.dialect.name != 'postgresql':
+        op.alter_column('ps_endpoints', '100rel',
+                        type_=old_type,
+                        existing_type=new_type)
+    else:
+        enum = ENUM(*OLD_ENUM, name='pjsip_100rel_values')
+        enum.create(op.get_bind(), checkfirst=False)
+
+        op.execute('ALTER TABLE ps_endpoints ALTER COLUMN 100rel TYPE'
+                   ' pjsip_100rel_values USING'
+                   ' 100rel::text::pjsip_100rel_values')
+
+        ENUM(name="pjsip_100rel_values_v2").drop(op.get_bind(), checkfirst=False)
diff --git a/contrib/ast-db-manage/config/versions/58e440314c2a_allow_wildcard_certs.py b/contrib/ast-db-manage/config/versions/58e440314c2a_allow_wildcard_certs.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e56f73160a2f8fca66e2bb233e26c11bd8fa9c5
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/58e440314c2a_allow_wildcard_certs.py
@@ -0,0 +1,29 @@
+"""allow_wildcard_certs
+
+Revision ID: 58e440314c2a
+Revises: 18e0805d367f
+Create Date: 2022-05-12 12:15:55.343743
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '58e440314c2a'
+down_revision = '18e0805d367f'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+YESNO_NAME = 'yesno_values'
+YESNO_VALUES = ['yes', 'no']
+
+def upgrade():
+    yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False)
+
+    op.add_column('ps_transports', sa.Column('allow_wildcard_certs', type_=yesno_values))
+
+
+def downgrade():
+    if op.get_context().bind.dialect.name == 'mssql':
+        op.drop_constraint('ck_ps_transports_allow_wildcard_certs_yesno_values', 'ps_transports')
+    op.drop_column('ps_transports', 'allow_wildcard_certs')
diff --git a/contrib/ast-db-manage/config/versions/5a2247c957d2_add_aoc_option.py b/contrib/ast-db-manage/config/versions/5a2247c957d2_add_aoc_option.py
new file mode 100644
index 0000000000000000000000000000000000000000..a603e7fb5482c32283c03e4615b84f61081f9401
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/5a2247c957d2_add_aoc_option.py
@@ -0,0 +1,38 @@
+"""add aoc option
+
+Revision ID: 5a2247c957d2
+Revises: ccf795ee535f
+Create Date: 2022-10-30 12:47:56.173511
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '5a2247c957d2'
+down_revision = 'ccf795ee535f'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+AST_BOOL_NAME = 'ast_bool_values'
+# We'll just ignore the n/y and f/t abbreviations as Asterisk does not write
+# those aliases.
+AST_BOOL_VALUES = [ '0', '1',
+                    'off', 'on',
+                    'false', 'true',
+                    'no', 'yes' ]
+
+def upgrade():
+    ############################# Enums ##############################
+
+    # ast_bool_values has already been created, so use postgres enum object
+    # type to get around "already created" issue - works okay with mysql
+    ast_bool_values = ENUM(*AST_BOOL_VALUES, name=AST_BOOL_NAME, create_type=False)
+
+    op.add_column('ps_endpoints', sa.Column('send_aoc', ast_bool_values))
+
+def downgrade():
+    if op.get_context().bind.dialect.name == 'mssql':
+        op.drop_constraint('ck_ps_endpoints_send_aoc_ast_bool_values', 'ps_endpoints')
+    op.drop_column('ps_endpoints', 'send_aoc')
+    pass
diff --git a/contrib/ast-db-manage/config/versions/7197536bb68d_geoloc_endpoint_params.py b/contrib/ast-db-manage/config/versions/7197536bb68d_geoloc_endpoint_params.py
new file mode 100644
index 0000000000000000000000000000000000000000..5159ff48f19d35bf271e5c05eacac594cfec6ff7
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/7197536bb68d_geoloc_endpoint_params.py
@@ -0,0 +1,22 @@
+"""Geoloc Endpoint Params
+
+Revision ID: 7197536bb68d
+Revises: 58e440314c2a
+Create Date: 2022-03-07 05:32:54.909429
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '7197536bb68d'
+down_revision = '58e440314c2a'
+
+from alembic import op
+import sqlalchemy as sa
+
+def upgrade():
+    op.add_column('ps_endpoints', sa.Column('geoloc_incoming_call_profile', sa.String(80)))
+    op.add_column('ps_endpoints', sa.Column('geoloc_outgoing_call_profile', sa.String(80)))
+
+def downgrade():
+    op.drop_column('ps_endpoints', 'geoloc_outgoing_call_profile')
+    op.drop_column('ps_endpoints', 'geoloc_incoming_call_profile')
diff --git a/contrib/ast-db-manage/config/versions/9f3692b1654b_add_stir_shaken_profile_and_codec_.py b/contrib/ast-db-manage/config/versions/9f3692b1654b_add_stir_shaken_profile_and_codec_.py
new file mode 100644
index 0000000000000000000000000000000000000000..e32c6851a5c68a8b95864ae6490a089f9fbb146b
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/9f3692b1654b_add_stir_shaken_profile_and_codec_.py
@@ -0,0 +1,58 @@
+"""Add Stir Shaken Profile and Codec Preference to ps endpoint
+
+Revision ID: 9f3692b1654b
+Revises: 7197536bb68d
+Create Date: 2022-08-17 11:20:56.433088
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '9f3692b1654b'
+down_revision = '7197536bb68d'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+PJSIP_INCOMING_CALL_OFFER_PREF_NAME ='pjsip_incoming_call_offer_pref_values'
+PJSIP_INCOMING_CALL_OFFER_PREF_VALUES = ['local', 'local_first',
+                                         'remote', 'remote_first']
+
+PJSIP_OUTGOING_CALL_OFFER_PREF_NAME = 'pjsip_outgoing_call_offer_pref_values'
+PJSIP_OUTGOING_CALL_OFFER_PREF_VALUES = ['local', 'local_merge', 'local_first',
+                                         'remote', 'remote_merge', 'remote_first']
+
+def upgrade():
+    context = op.get_context()
+
+    if context.bind.dialect.name == 'postgresql':
+        enum_in = ENUM(*PJSIP_INCOMING_CALL_OFFER_PREF_VALUES, name=PJSIP_INCOMING_CALL_OFFER_PREF_NAME)
+        enum_out = ENUM(*PJSIP_OUTGOING_CALL_OFFER_PREF_VALUES, name=PJSIP_OUTGOING_CALL_OFFER_PREF_NAME)
+
+        enum_in.create(op.get_bind(), checkfirst=False)
+        enum_out.create(op.get_bind(), checkfirst=False)
+
+    op.add_column('ps_endpoints', sa.Column('incoming_call_offer_pref',
+            sa.Enum(*PJSIP_INCOMING_CALL_OFFER_PREF_VALUES, name=PJSIP_INCOMING_CALL_OFFER_PREF_NAME)))
+
+    op.add_column('ps_endpoints', sa.Column('outgoing_call_offer_pref',
+            sa.Enum(*PJSIP_OUTGOING_CALL_OFFER_PREF_VALUES, name=PJSIP_OUTGOING_CALL_OFFER_PREF_NAME)))
+
+    op.add_column('ps_endpoints', sa.Column('stir_shaken_profile', sa.String(80)))
+
+def downgrade():
+    context = op.get_context()
+
+    if context.bind.dialect.name == 'mssql':
+        op.drop_constraint('ck_ps_endpoints_incoming_call_offer_pref_pjsip_incoming_call_offer_pref_values', 'ps_endpoints')
+        op.drop_constraint('ck_ps_endpoints_outgoing_call_offer_pref_pjsip_outgoing_call_offer_pref_values', 'ps_endpoints')
+
+    op.drop_column('ps_endpoints', 'outgoing_call_offer_pref')
+    op.drop_column('ps_endpoints', 'incoming_call_offer_pref')
+    op.drop_column('ps_endpoints', 'stir_shaken_profile')
+
+    enums = [PJSIP_INCOMING_CALL_OFFER_PREF_NAME, PJSIP_OUTGOING_CALL_OFFER_PREF_NAME]
+
+    if context.bind.dialect.name == 'postgresql':
+        for e in enums:
+            ENUM(name=e).drop(op.get_bind(), checkfirst=False)
\ No newline at end of file
diff --git a/contrib/ast-db-manage/config/versions/ccf795ee535f_all_codecs_on_empty_reinvite.py b/contrib/ast-db-manage/config/versions/ccf795ee535f_all_codecs_on_empty_reinvite.py
new file mode 100644
index 0000000000000000000000000000000000000000..125684f246dc865d7a05f27db91022fef0bbadf7
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/ccf795ee535f_all_codecs_on_empty_reinvite.py
@@ -0,0 +1,37 @@
+"""all_codecs_on_empty_reinvite
+
+Revision ID: ccf795ee535f
+Revises: 539f68bede2c
+Create Date: 2022-09-28 09:14:36.709781
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'ccf795ee535f'
+down_revision = '417c0247fd7e'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+AST_BOOL_NAME = 'ast_bool_values'
+# We'll just ignore the n/y and f/t abbreviations as Asterisk does not write
+# those aliases.
+AST_BOOL_VALUES = [ '0', '1',
+                    'off', 'on',
+                    'false', 'true',
+                    'no', 'yes' ]
+
+def upgrade():
+    ############################# Enums ##############################
+
+    # ast_bool_values has already been created, so use postgres enum object
+    # type to get around "already created" issue - works okay with mysql
+    ast_bool_values = ENUM(*AST_BOOL_VALUES, name=AST_BOOL_NAME, create_type=False)
+
+    op.add_column('ps_globals', sa.Column('all_codecs_on_empty_reinvite', ast_bool_values))
+
+def downgrade():
+    if op.get_context().bind.dialect.name == 'mssql':
+        op.drop_constraint('ck_ps_globals_all_codecs_on_empty_reinvite_ast_bool_values', 'ps_globals')
+    op.drop_column('ps_globals', 'all_codecs_on_empty_reinvite')
diff --git a/contrib/ast-db-manage/config/versions/f261363a857f_add_overlap_context.py b/contrib/ast-db-manage/config/versions/f261363a857f_add_overlap_context.py
new file mode 100644
index 0000000000000000000000000000000000000000..e0ea297a3c587655029163f3eefbe781c16e378a
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/f261363a857f_add_overlap_context.py
@@ -0,0 +1,21 @@
+"""add overlap_context
+
+Revision ID: f261363a857f
+Revises: 5a2247c957d2
+Create Date: 2022-12-09 13:58:48.622000
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'f261363a857f'
+down_revision = '5a2247c957d2'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    op.add_column('ps_endpoints', sa.Column('overlap_context', sa.String(80)))
+
+def downgrade():
+    op.drop_column('ps_endpoints', 'overlap_context')
diff --git a/contrib/init.d/rc.archlinux.asterisk b/contrib/init.d/rc.archlinux.asterisk
index 9c840851c8169f08d8a99b2ee024353dd665b3c1..47685adf04a667d91d2c90b010cb004cdf0e1982 100755
--- a/contrib/init.d/rc.archlinux.asterisk
+++ b/contrib/init.d/rc.archlinux.asterisk
@@ -45,7 +45,7 @@ case "$1" in
   stop)
     if [ -r ${ASTVARRUNDIR}/asterisk.pid ]; then
       stat_busy "Stopping Asterisk..."
-      ${DAEMON} -rx "core stop now" > /dev/null 2&>1
+      ${DAEMON} -rx "core stop now" &>/dev/null
       if [ $? -gt 0 ]; then
         stat_fail
       else
diff --git a/contrib/realtime/mysql/mysql_config.sql b/contrib/realtime/mysql/mysql_config.sql
index 67482a6b91d4c58a85ff866018f46181e38c8430..c08b26a890b5c303c753dbe6d50ad8cc409bf0a7 100644
--- a/contrib/realtime/mysql/mysql_config.sql
+++ b/contrib/realtime/mysql/mysql_config.sql
@@ -1334,3 +1334,75 @@ ALTER TABLE ps_resource_list ADD COLUMN resource_display_name ENUM('0','1','off'
 
 UPDATE alembic_version SET version_num='8f72185e437f' WHERE alembic_version.version_num = 'a06d8f8462d9';
 
+-- Running upgrade 8f72185e437f -> 0bee61aa9425
+
+ALTER TABLE ps_globals ADD COLUMN allow_sending_180_after_183 ENUM('0','1','off','on','false','true','no','yes');
+
+UPDATE alembic_version SET version_num='0bee61aa9425' WHERE alembic_version.version_num = '8f72185e437f';
+
+-- Running upgrade 0bee61aa9425 -> 18e0805d367f
+
+ALTER TABLE ps_registrations ADD COLUMN max_random_initial_delay INTEGER;
+
+UPDATE alembic_version SET version_num='18e0805d367f' WHERE alembic_version.version_num = '0bee61aa9425';
+
+-- Running upgrade 18e0805d367f -> 58e440314c2a
+
+ALTER TABLE ps_transports ADD COLUMN allow_wildcard_certs ENUM('yes','no');
+
+UPDATE alembic_version SET version_num='58e440314c2a' WHERE alembic_version.version_num = '18e0805d367f';
+
+-- Running upgrade 58e440314c2a -> 7197536bb68d
+
+ALTER TABLE ps_endpoints ADD COLUMN geoloc_incoming_call_profile VARCHAR(80);
+
+ALTER TABLE ps_endpoints ADD COLUMN geoloc_outgoing_call_profile VARCHAR(80);
+
+UPDATE alembic_version SET version_num='7197536bb68d' WHERE alembic_version.version_num = '58e440314c2a';
+
+-- Running upgrade 7197536bb68d -> 9f3692b1654b
+
+ALTER TABLE ps_endpoints ADD COLUMN incoming_call_offer_pref ENUM('local','local_first','remote','remote_first');
+
+ALTER TABLE ps_endpoints ADD COLUMN outgoing_call_offer_pref ENUM('local','local_merge','local_first','remote','remote_merge','remote_first');
+
+ALTER TABLE ps_endpoints ADD COLUMN stir_shaken_profile VARCHAR(80);
+
+UPDATE alembic_version SET version_num='9f3692b1654b' WHERE alembic_version.version_num = '7197536bb68d';
+
+-- Running upgrade 9f3692b1654b -> 539f68bede2c
+
+ALTER TABLE ps_endpoints MODIFY `100rel` ENUM('no','required','peer_supported','yes') NULL;
+
+UPDATE alembic_version SET version_num='539f68bede2c' WHERE alembic_version.version_num = '9f3692b1654b';
+
+-- Running upgrade 539f68bede2c -> 417c0247fd7e
+
+ALTER TABLE ps_endpoints ADD COLUMN security_negotiation ENUM('no','mediasec');
+
+ALTER TABLE ps_endpoints ADD COLUMN security_mechanisms VARCHAR(512);
+
+ALTER TABLE ps_registrations ADD COLUMN security_negotiation ENUM('no','mediasec');
+
+ALTER TABLE ps_registrations ADD COLUMN security_mechanisms VARCHAR(512);
+
+UPDATE alembic_version SET version_num='417c0247fd7e' WHERE alembic_version.version_num = '539f68bede2c';
+
+-- Running upgrade 417c0247fd7e -> ccf795ee535f
+
+ALTER TABLE ps_globals ADD COLUMN all_codecs_on_empty_reinvite ENUM('0','1','off','on','false','true','no','yes');
+
+UPDATE alembic_version SET version_num='ccf795ee535f' WHERE alembic_version.version_num = '417c0247fd7e';
+
+-- Running upgrade ccf795ee535f -> 5a2247c957d2
+
+ALTER TABLE ps_endpoints ADD COLUMN send_aoc ENUM('0','1','off','on','false','true','no','yes');
+
+UPDATE alembic_version SET version_num='5a2247c957d2' WHERE alembic_version.version_num = 'ccf795ee535f';
+
+-- Running upgrade 5a2247c957d2 -> f261363a857f
+
+ALTER TABLE ps_endpoints ADD COLUMN overlap_context VARCHAR(80);
+
+UPDATE alembic_version SET version_num='f261363a857f' WHERE alembic_version.version_num = '5a2247c957d2';
+
diff --git a/contrib/realtime/mysql/mysql_queue_log.sql b/contrib/realtime/mysql/mysql_queue_log.sql
new file mode 100644
index 0000000000000000000000000000000000000000..13dde964c29793ddd8452b0f1a1dcf21ac971ac9
--- /dev/null
+++ b/contrib/realtime/mysql/mysql_queue_log.sql
@@ -0,0 +1,29 @@
+BEGIN;
+
+CREATE TABLE alembic_version (
+    version_num VARCHAR(32) NOT NULL, 
+    CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)
+);
+
+-- Running upgrade  -> 4105ee839f58
+
+CREATE TABLE queue_log (
+    id BIGSERIAL NOT NULL, 
+    time TIMESTAMP WITHOUT TIME ZONE, 
+    callid VARCHAR(80), 
+    queuename VARCHAR(256), 
+    agent VARCHAR(80), 
+    event VARCHAR(32), 
+    data1 VARCHAR(100), 
+    data2 VARCHAR(100), 
+    data3 VARCHAR(100), 
+    data4 VARCHAR(100), 
+    data5 VARCHAR(100), 
+    PRIMARY KEY (id), 
+    UNIQUE (id)
+);
+
+INSERT INTO alembic_version (version_num) VALUES ('4105ee839f58');
+
+COMMIT;
+
diff --git a/contrib/realtime/postgresql/postgresql_config.sql b/contrib/realtime/postgresql/postgresql_config.sql
index 293a4f977070005fd0c66d8492f4f16fb99e8379..aa14f92a81e58dd6742ca983f38dc59d8761544b 100644
--- a/contrib/realtime/postgresql/postgresql_config.sql
+++ b/contrib/realtime/postgresql/postgresql_config.sql
@@ -1444,5 +1444,87 @@ ALTER TABLE ps_resource_list ADD COLUMN resource_display_name ast_bool_values;
 
 UPDATE alembic_version SET version_num='8f72185e437f' WHERE alembic_version.version_num = 'a06d8f8462d9';
 
+-- Running upgrade 8f72185e437f -> 0bee61aa9425
+
+ALTER TABLE ps_globals ADD COLUMN allow_sending_180_after_183 ast_bool_values;
+
+UPDATE alembic_version SET version_num='0bee61aa9425' WHERE alembic_version.version_num = '8f72185e437f';
+
+-- Running upgrade 0bee61aa9425 -> 18e0805d367f
+
+ALTER TABLE ps_registrations ADD COLUMN max_random_initial_delay INTEGER;
+
+UPDATE alembic_version SET version_num='18e0805d367f' WHERE alembic_version.version_num = '0bee61aa9425';
+
+-- Running upgrade 18e0805d367f -> 58e440314c2a
+
+ALTER TABLE ps_transports ADD COLUMN allow_wildcard_certs yesno_values;
+
+UPDATE alembic_version SET version_num='58e440314c2a' WHERE alembic_version.version_num = '18e0805d367f';
+
+-- Running upgrade 58e440314c2a -> 7197536bb68d
+
+ALTER TABLE ps_endpoints ADD COLUMN geoloc_incoming_call_profile VARCHAR(80);
+
+ALTER TABLE ps_endpoints ADD COLUMN geoloc_outgoing_call_profile VARCHAR(80);
+
+UPDATE alembic_version SET version_num='7197536bb68d' WHERE alembic_version.version_num = '58e440314c2a';
+
+-- Running upgrade 7197536bb68d -> 9f3692b1654b
+
+CREATE TYPE pjsip_incoming_call_offer_pref_values AS ENUM ('local', 'local_first', 'remote', 'remote_first');
+
+CREATE TYPE pjsip_outgoing_call_offer_pref_values AS ENUM ('local', 'local_merge', 'local_first', 'remote', 'remote_merge', 'remote_first');
+
+ALTER TABLE ps_endpoints ADD COLUMN incoming_call_offer_pref pjsip_incoming_call_offer_pref_values;
+
+ALTER TABLE ps_endpoints ADD COLUMN outgoing_call_offer_pref pjsip_outgoing_call_offer_pref_values;
+
+ALTER TABLE ps_endpoints ADD COLUMN stir_shaken_profile VARCHAR(80);
+
+UPDATE alembic_version SET version_num='9f3692b1654b' WHERE alembic_version.version_num = '7197536bb68d';
+
+-- Running upgrade 9f3692b1654b -> 539f68bede2c
+
+CREATE TYPE pjsip_100rel_values_v2 AS ENUM ('no', 'required', 'peer_supported', 'yes');
+
+ALTER TABLE ps_endpoints ALTER COLUMN 100rel TYPE pjsip_100rel_values_v2 USING 100rel::text::pjsip_100rel_values_v2;
+
+DROP TYPE pjsip_100rel_values;
+
+UPDATE alembic_version SET version_num='539f68bede2c' WHERE alembic_version.version_num = '9f3692b1654b';
+
+-- Running upgrade 539f68bede2c -> 417c0247fd7e
+
+CREATE TYPE security_negotiation_values AS ENUM ('no', 'mediasec');
+
+ALTER TABLE ps_endpoints ADD COLUMN security_negotiation security_negotiation_values;
+
+ALTER TABLE ps_endpoints ADD COLUMN security_mechanisms VARCHAR(512);
+
+ALTER TABLE ps_registrations ADD COLUMN security_negotiation security_negotiation_values;
+
+ALTER TABLE ps_registrations ADD COLUMN security_mechanisms VARCHAR(512);
+
+UPDATE alembic_version SET version_num='417c0247fd7e' WHERE alembic_version.version_num = '539f68bede2c';
+
+-- Running upgrade 417c0247fd7e -> ccf795ee535f
+
+ALTER TABLE ps_globals ADD COLUMN all_codecs_on_empty_reinvite ast_bool_values;
+
+UPDATE alembic_version SET version_num='ccf795ee535f' WHERE alembic_version.version_num = '417c0247fd7e';
+
+-- Running upgrade ccf795ee535f -> 5a2247c957d2
+
+ALTER TABLE ps_endpoints ADD COLUMN send_aoc ast_bool_values;
+
+UPDATE alembic_version SET version_num='5a2247c957d2' WHERE alembic_version.version_num = 'ccf795ee535f';
+
+-- Running upgrade 5a2247c957d2 -> f261363a857f
+
+ALTER TABLE ps_endpoints ADD COLUMN overlap_context VARCHAR(80);
+
+UPDATE alembic_version SET version_num='f261363a857f' WHERE alembic_version.version_num = '5a2247c957d2';
+
 COMMIT;
 
diff --git a/contrib/realtime/postgresql/postgresql_queue_log.sql b/contrib/realtime/postgresql/postgresql_queue_log.sql
new file mode 100644
index 0000000000000000000000000000000000000000..13dde964c29793ddd8452b0f1a1dcf21ac971ac9
--- /dev/null
+++ b/contrib/realtime/postgresql/postgresql_queue_log.sql
@@ -0,0 +1,29 @@
+BEGIN;
+
+CREATE TABLE alembic_version (
+    version_num VARCHAR(32) NOT NULL, 
+    CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)
+);
+
+-- Running upgrade  -> 4105ee839f58
+
+CREATE TABLE queue_log (
+    id BIGSERIAL NOT NULL, 
+    time TIMESTAMP WITHOUT TIME ZONE, 
+    callid VARCHAR(80), 
+    queuename VARCHAR(256), 
+    agent VARCHAR(80), 
+    event VARCHAR(32), 
+    data1 VARCHAR(100), 
+    data2 VARCHAR(100), 
+    data3 VARCHAR(100), 
+    data4 VARCHAR(100), 
+    data5 VARCHAR(100), 
+    PRIMARY KEY (id), 
+    UNIQUE (id)
+);
+
+INSERT INTO alembic_version (version_num) VALUES ('4105ee839f58');
+
+COMMIT;
+
diff --git a/contrib/scripts/ast_grab_core b/contrib/scripts/ast_grab_core
old mode 100644
new mode 100755
index bc56b618783c5b55c579ffcdf9228922624c3403..6701a2d4e0f7292ce5720112fa10a8b7970448df
--- a/contrib/scripts/ast_grab_core
+++ b/contrib/scripts/ast_grab_core
@@ -67,4 +67,4 @@ echo Notifying admins of the core.
 echo Done.
 echo
 echo Reproducible deadlocks should be posted with a full backtrace and instructions
-echo to reproduce the issue at https://issues.asterisk.org/    Thanks!
+echo to reproduce the issue at https://github.com/asterisk/asterisk/issues/    Thanks!
diff --git a/contrib/scripts/asterisk.logrotate b/contrib/scripts/asterisk.logrotate
index 92e6548e733ae42856d48bd5530db09bb0395fa7..f774446803b3dbe2bef7abf0027d2018b1da1fdd 100644
--- a/contrib/scripts/asterisk.logrotate
+++ b/contrib/scripts/asterisk.logrotate
@@ -1,4 +1,4 @@
-__LOGDIR__/debug __LOGDIR__/console __LOGDIR__/full __LOGDIR__/messages __LOGDIR__/*log {
+__LOGDIR__/debug.log __LOGDIR__/console __LOGDIR__/full.log __LOGDIR__/messages.log __LOGDIR__/*log {
 	weekly
 	missingok
 	rotate 52
diff --git a/contrib/scripts/install_prereq b/contrib/scripts/install_prereq
index 98e9350fce99493fd084306ea38d28c1c6e7023d..ae3857ae0bb5440d16d2d1f0c4b2267c69023a23 100755
--- a/contrib/scripts/install_prereq
+++ b/contrib/scripts/install_prereq
@@ -25,7 +25,7 @@ PACKAGES_DEBIAN="$PACKAGES_DEBIAN libedit-dev libjansson-dev libsqlite3-dev uuid
 # Asterisk: for addons:
 PACKAGES_DEBIAN="$PACKAGES_DEBIAN libspeex-dev libspeexdsp-dev libogg-dev libvorbis-dev libasound2-dev portaudio19-dev libcurl4-openssl-dev xmlstarlet bison flex"
 PACKAGES_DEBIAN="$PACKAGES_DEBIAN libpq-dev unixodbc-dev libneon27-dev libgmime-2.6-dev libgmime-3.0-dev liblua5.2-dev liburiparser-dev libxslt1-dev libssl-dev"
-PACKAGES_DEBIAN="$PACKAGES_DEBIAN libvpb-dev libmysqlclient-dev libbluetooth-dev libradcli-dev freetds-dev libosptk-dev libjack-jackd2-dev bash libcap-dev"
+PACKAGES_DEBIAN="$PACKAGES_DEBIAN libmysqlclient-dev libbluetooth-dev libradcli-dev freetds-dev libosptk-dev libjack-jackd2-dev bash libcap-dev"
 PACKAGES_DEBIAN="$PACKAGES_DEBIAN libsnmp-dev libiksemel-dev libcorosync-common-dev libcpg-dev libcfg-dev libnewt-dev libpopt-dev libical-dev libspandsp-dev"
 PACKAGES_DEBIAN="$PACKAGES_DEBIAN libresample1-dev libc-client2007e-dev binutils-dev libsrtp0-dev libsrtp2-dev libgsm1-dev doxygen graphviz zlib1g-dev libldap2-dev"
 PACKAGES_DEBIAN="$PACKAGES_DEBIAN libcodec2-dev libfftw3-dev libsndfile1-dev libunbound-dev"
@@ -270,6 +270,43 @@ handle_debian() {
 	fi
 }
 
+handle_linuxmint() {
+	# Store temporary variable to contain list of missing packages
+	missing_packages=""
+
+	# Check installed packages for Mint
+	for package in $PACKAGES_DEBIAN; do
+		if [ "$package" = "libgmime-2.6-dev" ] || [ "$package" = "libsrtp0-dev" ]; then
+			continue
+		fi
+
+		missing_package_check=$(apt list --installed 2>/dev/null | grep -c $package)
+
+		if [ "$missing_package_check" -eq 0 ]; then
+			echo "This package is missing: "$package
+			missing_packages="${missing_packages} $package"
+		else
+			echo "Package "$package" is present."
+		fi
+	done
+
+	# Update the packages on our local system
+	echo "==========================="
+	echo "Updating your system's packages"
+	echo "==========================="
+	$testcmd apt update
+
+	# Inform user of packages to be installed
+	echo "==========================="
+	echo "The following packages will be installed on your system:"
+	echo $missing_packages
+	echo "==========================="
+
+	if [ "$missing_packages" != "" ] ; then
+		$testcmd apt -m install -y $missing_packages
+	fi
+}
+
 handle_rh() {
 	extra_packs=`check_installed_rpms $PACKAGES_RH`
 	if [ x"$extra_packs" != "x" ] ; then
@@ -331,12 +368,6 @@ handle_SUSE() {
 }
 
 install_unpackaged() {
-	echo "*** Installing NBS (Network Broadcast Sound) ***"
-	svn co https://svn.digium.com/svn/nbs/trunk nbs-trunk
-	cd nbs-trunk
-	make all install
-	cd ..
-
 	# Only install libresample if it wasn't installed via package
 	if ! test -f /usr/include/libresample.h; then
 		echo "*** Installing libresample ***"
@@ -411,7 +442,11 @@ fi
 
 # The distributions we do support:
 if [ -r /etc/debian_version ]; then
-	handle_debian
+	if [ -f /etc/linuxmint/info ]; then
+		handle_linuxmint
+	else
+		handle_debian
+	fi
 elif [ -r /etc/redhat-release ]; then
 	handle_rh
 elif [ -f /etc/SuSE-release -o -f /etc/novell-release ]; then
diff --git a/contrib/scripts/sip_to_pjsip/sip_to_pjsip.py b/contrib/scripts/sip_to_pjsip/sip_to_pjsip.py
index e7c3d8fa3ff5d6cc183bdd2d4d7d3723c0739392..10f6b33d1be887d905d216ee270f0b94730b8dde 100755
--- a/contrib/scripts/sip_to_pjsip/sip_to_pjsip.py
+++ b/contrib/scripts/sip_to_pjsip/sip_to_pjsip.py
@@ -1321,7 +1321,7 @@ if __name__ == "__main__":
     # configuration parser for sip.conf
     sip = astconfigparser.MultiOrderedConfigParser()
     info('Please, report any issue at:')
-    info('    https://issues.asterisk.org/')
+    info('    https://github.com/asterisk/asterisk/issues/')
     info('Reading ' + sip_filename)
     sip.read(sip_filename)
     info('Converting to PJSIP...')
diff --git a/contrib/scripts/sip_to_pjsip/sip_to_pjsql.py b/contrib/scripts/sip_to_pjsip/sip_to_pjsql.py
index 71ddd45b64ddbb2fc2c68021eefc7dc3596808b8..2469ffc9fe61cfac0f0fcff3e7de3d1691fcd5d3 100755
--- a/contrib/scripts/sip_to_pjsip/sip_to_pjsql.py
+++ b/contrib/scripts/sip_to_pjsip/sip_to_pjsql.py
@@ -70,7 +70,7 @@ if __name__ == "__main__":
     sip_to_pjsip.sip = sip
     sip.connect(user,password,host,port,database)
     print('Please, report any issue at:')
-    print('    https://issues.asterisk.org/')
+    print('    https://github.com/asterisk/asterisk/issues/')
     print('Reading ' + sip_filename)
     sip.read(sip_filename)
     print('Converting to PJSIP realtime sql...')
diff --git a/doc/CHANGES-staging/ami_hook_flash.txt b/doc/CHANGES-staging/ami_hook_flash.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5bf1e3455e44196782102ebcdddb08652c36e752
--- /dev/null
+++ b/doc/CHANGES-staging/ami_hook_flash.txt
@@ -0,0 +1,4 @@
+Subject: app_senddtmf
+
+The SendFlash AMI action now allows sending
+a hook flash event on a channel.
diff --git a/doc/CHANGES-staging/app_mixmonitor_mute_by_id.txt b/doc/CHANGES-staging/app_mixmonitor_mute_by_id.txt
new file mode 100644
index 0000000000000000000000000000000000000000..958a914ba9da2fb4ae3d0bd482097dd9e29d04c5
--- /dev/null
+++ b/doc/CHANGES-staging/app_mixmonitor_mute_by_id.txt
@@ -0,0 +1,17 @@
+Subject: app_mixmonitor
+Subject: audiohook
+Subject: manager
+
+It is now possible to specify the MixMonitorID when calling
+the manager action: MixMonitorMute.  This will allow an
+individual MixMonitor instance to be muted via ID.
+
+The MixMonitorID can be stored as a channel variable using
+the 'i' MixMonitor option and is returned upon creation if
+this option is used.
+
+As part of this change, if no MixMonitorID is specified in
+the manager action MixMonitorMute, Asterisk will set the mute
+flag on all MixMonitor audiohooks on the channel.  Previous
+behavior would set the flag on the first MixMonitor audiohook
+found.
diff --git a/doc/CHANGES-staging/bridge_builtin_features_beep_on_monitor.txt b/doc/CHANGES-staging/bridge_builtin_features_beep_on_monitor.txt
new file mode 100644
index 0000000000000000000000000000000000000000..39bf9a72c08069db9d3ab0970f5205ab4e5bd01a
--- /dev/null
+++ b/doc/CHANGES-staging/bridge_builtin_features_beep_on_monitor.txt
@@ -0,0 +1,12 @@
+Subject: bridge_builtin_features
+
+Add optional touch variable : TOUCH_MIXMONITOR_BEEP(interval)
+
+Setting TOUCH_MIXMONITOR_BEEP/TOUCH_MONITOR_BEEP to a valid
+interval in seconds will result in a periodic beep being
+played to the monitored channel upon MixMontior/Monitor
+feature start.
+
+If an interval less than 5 seconds is specified, the interval
+will default to 5 seconds.  If the value is set to an invalid
+interval, the default of 15 seconds will be used.
diff --git a/doc/CHANGES-staging/cli_channel_display_length_increase.txt b/doc/CHANGES-staging/cli_channel_display_length_increase.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d9fc77cd88e1e0d4daba92cf84589f9d9f6c6f3a
--- /dev/null
+++ b/doc/CHANGES-staging/cli_channel_display_length_increase.txt
@@ -0,0 +1,14 @@
+Subject: cli
+Subject: core
+
+This change increases the display width on 'core show channels'
+amd 'core show channels verbose'
+
+For 'core show channels', the Channel name field is increased to
+64 characters and the Location name field is increased to 32
+characters.
+
+For 'core show channels verbose', the Channel name field is
+increased to 80 characters, the Context is increased to 24
+characters and the Extension is increased to 24 characters.
+
diff --git a/doc/CHANGES-staging/dundi.txt b/doc/CHANGES-staging/dundi.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e71f72674335f5769acaf10e3bc82ef2b317234e
--- /dev/null
+++ b/doc/CHANGES-staging/dundi.txt
@@ -0,0 +1,5 @@
+Subject: DUNDi
+
+DUNDi now supports chan_pjsip. Outgoing calls using
+PJSIP require the pjsip_outgoing_endpoint option
+to be set in dundi.conf.
diff --git a/doc/CHANGES-staging/format_sln_support_for_slin.txt b/doc/CHANGES-staging/format_sln_support_for_slin.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3d66536ccde699695ec0d237cd37a3523d0e9f82
--- /dev/null
+++ b/doc/CHANGES-staging/format_sln_support_for_slin.txt
@@ -0,0 +1,5 @@
+Subject: format_sln
+
+format_sln now recognizes '.slin' as a valid
+file extension in addition to the existing
+'.sln' and '.raw'.
diff --git a/doc/CHANGES-staging/res_http_media_cache.txt b/doc/CHANGES-staging/res_http_media_cache.txt
new file mode 100644
index 0000000000000000000000000000000000000000..79223c03394558ff8e6bcda49e4557874ae0252e
--- /dev/null
+++ b/doc/CHANGES-staging/res_http_media_cache.txt
@@ -0,0 +1,12 @@
+Subject: res_http_media_cache
+
+The res_http_media_cache module now attempts to load
+configuration from the res_http_media_cache.conf file.
+The following options were added:
+  * timeout_secs
+  * user_agent
+  * follow_location
+  * max_redirects
+  * protocols
+  * redirect_protocols
+  * dns_cache_timeout_secs
diff --git a/doc/CHANGES-staging/test.txt b/doc/CHANGES-staging/test.txt
new file mode 100644
index 0000000000000000000000000000000000000000..05a2694b31b6f49f94c07f84a521f6ca76ce365c
--- /dev/null
+++ b/doc/CHANGES-staging/test.txt
@@ -0,0 +1,11 @@
+Subject: test.c
+
+The "tests" attribute of the "testsuite" element in the
+output XML now reflects only the tests actually requested
+to be executed instead of all the tests registered.
+
+The "failures" attribute was added to the "testsuite"
+element.
+
+Also added two new unit tests that just pass and fail
+to be used for testing CI itself.
diff --git a/doc/appdocsxml.dtd b/doc/appdocsxml.dtd
index fbcad6ddfe03de37b3d9ff8a6011fc56e6692aac..426d959466cb1c29c4e1c427e6f1a631d68f1f9e 100644
--- a/doc/appdocsxml.dtd
+++ b/doc/appdocsxml.dtd
@@ -69,10 +69,10 @@
   <!ATTLIST configInfo name CDATA #REQUIRED>
   <!ATTLIST configInfo language CDATA #REQUIRED>
 
-  <!ELEMENT configFile (configObject+)>
+  <!ELEMENT configFile (configObject|xi:include)+>
   <!ATTLIST configFile name CDATA #REQUIRED>
 
-  <!ELEMENT configObject (synopsis?|description?|syntax?|see-also?|configOption)*>
+  <!ELEMENT configObject (synopsis?|description?|syntax?|see-also?|(configOption|xi:include))*>
   <!ATTLIST configObject name CDATA #REQUIRED>
 
   <!ELEMENT configOption (synopsis,description?,syntax?,see-also?)*>
diff --git a/doc/asterisk.8 b/doc/asterisk.8
index e5991d9cf7dfbc96b252f7c9ff0d4a0a8ae92890..64ea625b477b9b4edaf4b3e47e32fb607840c837 100644
--- a/doc/asterisk.8
+++ b/doc/asterisk.8
@@ -158,6 +158,7 @@ too many simultaneous calls.
 .TP
 \-n
 Disable ANSI colors even on terminals capable of displaying them.
+This option can be used only at startup (e.g. not with remote console).
 .TP
 \-p
 If supported by the operating system (and executing as root),
@@ -195,7 +196,8 @@ then move them into the final location when done.
 .TP
 \-T
 Add timestamp to all non-command related output going to the console
-when running with verbose and/or logging to the console.
+when running with verbose and/or logging to the console. Can only be
+used at startup (e.g. not with remote console mode).
 .TP
 \-U \fIuser\fR
 Run as user \fIuser\fR instead of the
@@ -241,7 +243,7 @@ Enables executing of includes via \fB#exec\fR directive inside
 .PP
 \fBasterisk \-rx "core show channels"\fR - Display channels on running server
 .SH BUGS
-Bug reports and feature requests may be filed at https://issues.asterisk.org
+Bug reports and feature requests may be filed at https://github.com/asterisk/asterisk/issues/
 .SH "SEE ALSO"
 https://www.asterisk.org - The Asterisk Home Page
 .PP
diff --git a/doc/asterisk.sgml b/doc/asterisk.sgml
index 60450a014e70a5197fb89a21272218151d8a5c95..32a46e825800f3ef6e022c76e6da88340b86039d 100644
--- a/doc/asterisk.sgml
+++ b/doc/asterisk.sgml
@@ -415,7 +415,7 @@
  <refsect1>
   <title>BUGS</title>
   <para>
-  Bug reports and feature requests may be filed at https://issues.asterisk.org
+    Bug reports and feature requests may be filed at https://github.com/asterisk/asterisk/issues/
   </para>
  </refsect1>
  <refsect1>
diff --git a/formats/format_sln.c b/formats/format_sln.c
index 80d34840023cdca632950488002dba1fa8707af9..98985ba86a477ac2c56c9cd84de816cee1a7ceef 100644
--- a/formats/format_sln.c
+++ b/formats/format_sln.c
@@ -127,7 +127,7 @@ static off_t slinear_tell(struct ast_filestream *fs)
 static struct ast_frame *slinear_read(struct ast_filestream *s, int *whennext){return generic_read(s, whennext, 320);}
 static struct ast_format_def slin_f = {
 	.name = "sln",
-	.exts = "sln|raw",
+	.exts = "sln|slin|raw",
 	.write = slinear_write,
 	.seek = slinear_seek,
 	.trunc = slinear_trunc,
diff --git a/formats/format_wav.c b/formats/format_wav.c
index 886c8c137858ad671b1dc8d38f171f737127173f..afb452b6d848d550d73d07acf26c3f441a1329a9 100644
--- a/formats/format_wav.c
+++ b/formats/format_wav.c
@@ -189,7 +189,7 @@ static int check_header(FILE *f, int hz)
 		}
 		if(memcmp(buf, "data", 4) == 0 )
 			break;
-		ast_log(LOG_DEBUG, "Skipping unknown block '%.4s'\n", buf);
+		ast_debug(1, "Skipping unknown block '%.4s'\n", buf);
 		if (fseek(f,data,SEEK_CUR) == -1 ) {
 			ast_log(LOG_WARNING, "Failed to skip '%.4s' block: %d\n", buf, data);
 			return -1;
diff --git a/funcs/func_callerid.c b/funcs/func_callerid.c
index 64aeffcc4b892f954ff410ef2520fc3f852d303b..448f54696d34a2f1804fec0ace1d4651cbf23b72 100644
--- a/funcs/func_callerid.c
+++ b/funcs/func_callerid.c
@@ -1611,6 +1611,7 @@ static int redirecting_write(struct ast_channel *chan, const char *cmd, char *da
 			 * reason, so we can just set the reason string to what was given and set the
 			 * code to be unknown
 			 */
+				ast_log(LOG_WARNING, "Unknown redirecting reason '%s', defaulting to unknown\n", val);
 				redirecting.orig_reason.code = AST_REDIRECTING_REASON_UNKNOWN;
 				redirecting.orig_reason.str = val;
 				set_it(chan, &redirecting, NULL);
diff --git a/funcs/func_cdr.c b/funcs/func_cdr.c
index d7bc211452db9c207e18965e025edcfdf8c1496d..2c6e2e101859a4763e4512893346d7148a3f991e 100644
--- a/funcs/func_cdr.c
+++ b/funcs/func_cdr.c
@@ -161,7 +161,9 @@
 			<note><para>CDRs can only be modified before the bridge between two channels is
 			torn down. For example, CDRs may not be modified after the <literal>Dial</literal>
 			application has returned.</para></note>
-			<para>Example: exten => 1,1,Set(CDR(userfield)=test)</para>
+			<example title="Set the userfield">
+			 exten => 1,1,Set(CDR(userfield)=test)
+			</example>
 		</description>
 	</function>
 	<function name="CDR_PROP" language="en_US">
diff --git a/funcs/func_channel.c b/funcs/func_channel.c
index 49c4b057793375667ad949815c5cabea5b42a32c..c1c8e5b71bdf165763d5e51dbe3b0300d62a5914 100644
--- a/funcs/func_channel.c
+++ b/funcs/func_channel.c
@@ -64,6 +64,11 @@
 		</description>
 	</function>
 	<function name="CHANNEL_EXISTS" language="en_US">
+		<since>
+			<version>16.22.0</version>
+			<version>18.8.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Checks if the specified channel exists.
 		</synopsis>
diff --git a/funcs/func_db.c b/funcs/func_db.c
index 3f98ed060468570086173d665222935556825769..33d082178068d76d3cc26a5f56b0ab7ce6b135cc 100644
--- a/funcs/func_db.c
+++ b/funcs/func_db.c
@@ -95,6 +95,25 @@
 			at the prefix specified within the Asterisk database.  If no argument is
 			provided, then a list of key families will be returned.</para>
 		</description>
+		<see-also>
+			<ref type="function">DB_KEYCOUNT</ref>
+		</see-also>
+	</function>
+	<function name="DB_KEYCOUNT" language="en_US">
+		<synopsis>
+			Obtain the number of keys at a prefix within the Asterisk database.
+		</synopsis>
+		<syntax>
+			<parameter name="prefix" />
+		</syntax>
+		<description>
+			<para>This function will return the number of keys that exist
+			at the prefix specified within the Asterisk database.  If no argument is
+			provided, then the number of all key families will be returned.</para>
+		</description>
+		<see-also>
+			<ref type="function">DB_KEYS</ref>
+		</see-also>
 	</function>
 	<function name="DB_DELETE" language="en_US">
 		<synopsis>
@@ -286,6 +305,57 @@ static struct ast_custom_function db_keys_function = {
 	.read2 = function_db_keys,
 };
 
+static int function_db_keycount(struct ast_channel *chan, const char *cmd, char *parse, char *buf, size_t len)
+{
+	size_t parselen = strlen(parse);
+	struct ast_db_entry *dbe, *orig_dbe;
+	const char *last = "";
+	int keycount = 0;
+
+	/* Remove leading and trailing slashes */
+	while (parse[0] == '/') {
+		parse++;
+		parselen--;
+	}
+	while (parse[parselen - 1] == '/') {
+		parse[--parselen] = '\0';
+	}
+
+	/* Nothing within the database at that prefix? */
+	if (!(orig_dbe = dbe = ast_db_gettree(parse, NULL))) {
+		snprintf(buf, len, "%d", keycount);
+		return 0;
+	}
+
+	for (; dbe; dbe = dbe->next) {
+		/* Find the current component */
+		char *curkey = &dbe->key[parselen + 1], *slash;
+		if (*curkey == '/') {
+			curkey++;
+		}
+		/* Remove everything after the current component */
+		if ((slash = strchr(curkey, '/'))) {
+			*slash = '\0';
+		}
+
+		/* Skip duplicates */
+		if (!strcasecmp(last, curkey)) {
+			continue;
+		}
+		last = curkey;
+
+		keycount++;
+	}
+	ast_db_freetree(orig_dbe);
+	snprintf(buf, len, "%d", keycount);
+	return 0;
+}
+
+static struct ast_custom_function db_keycount_function = {
+	.name = "DB_KEYCOUNT",
+	.read = function_db_keycount,
+};
+
 static int function_db_delete(struct ast_channel *chan, const char *cmd,
 			      char *parse, char *buf, size_t len)
 {
@@ -347,6 +417,7 @@ static int unload_module(void)
 	res |= ast_custom_function_unregister(&db_exists_function);
 	res |= ast_custom_function_unregister(&db_delete_function);
 	res |= ast_custom_function_unregister(&db_keys_function);
+	res |= ast_custom_function_unregister(&db_keycount_function);
 
 	return res;
 }
@@ -359,6 +430,7 @@ static int load_module(void)
 	res |= ast_custom_function_register(&db_exists_function);
 	res |= ast_custom_function_register_escalating(&db_delete_function, AST_CFE_READ);
 	res |= ast_custom_function_register(&db_keys_function);
+	res |= ast_custom_function_register(&db_keycount_function);
 
 	return res;
 }
diff --git a/funcs/func_dialgroup.c b/funcs/func_dialgroup.c
index 9a98b3ca24d406cd00b3b82e5d858510578f2a27..64aa65882e7711601dcc94bce9fe298d10f80d72 100644
--- a/funcs/func_dialgroup.c
+++ b/funcs/func_dialgroup.c
@@ -67,10 +67,11 @@
 			fallback to a queue when the front line people are busy or unavailable, but
 			you still want front line people to log in and out of that group, just like
 			a queue.</para>
-			<para>Example:</para>
-			<para>exten => 1,1,Set(DIALGROUP(mygroup,add)=SIP/10)</para>
-			<para>exten => 1,n,Set(DIALGROUP(mygroup,add)=SIP/20)</para>
-			<para>exten => 1,n,Dial(${DIALGROUP(mygroup)})</para>
+			<example title="Add 2 endpoints to a dial group">
+			exten => 1,1,Set(DIALGROUP(mygroup,add)=SIP/10)
+			same => n,Set(DIALGROUP(mygroup,add)=SIP/20)
+			same => n,Dial(${DIALGROUP(mygroup)})
+			</example>
 		</description>
 	</function>
  ***/
diff --git a/funcs/func_env.c b/funcs/func_env.c
index f5ff3e0d3c7c45750ce01c03174b46736567d948..aba753352ee06570a159588b4142bd30d7062683 100644
--- a/funcs/func_env.c
+++ b/funcs/func_env.c
@@ -178,48 +178,61 @@
 		<description>
 			<para>Read and write text file in character and line mode.</para>
 			<para>Examples:</para>
-			<para/>
 			<para>Read mode (byte):</para>
-			<para>    ;reads the entire content of the file.</para>
-			<para>    Set(foo=${FILE(/tmp/test.txt)})</para>
-			<para>    ;reads from the 11th byte to the end of the file (i.e. skips the first 10).</para>
-			<para>    Set(foo=${FILE(/tmp/test.txt,10)})</para>
-			<para>    ;reads from the 11th to 20th byte in the file (i.e. skip the first 10, then read 10 bytes).</para>
-			<para>    Set(foo=${FILE(/tmp/test.txt,10,10)})</para>
-			<para/>
+			<example title="Reads the entire content of the file">
+			same => n,Set(foo=${FILE(/tmp/test.txt)})
+			</example>
+			<example title="Reads from the 11th byte to the end of the file (i.e. skips the first 10)">
+			same => n,Set(foo=${FILE(/tmp/test.txt,10)})
+			</example>
+			<example title="Reads from the 11th to 20th byte in the file (i.e. skip the first 10, then read 10 bytes)">
+			same => n,Set(foo=${FILE(/tmp/test.txt,10,10)})
+			</example>
 			<para>Read mode (line):</para>
-			<para>    ; reads the 3rd line of the file.</para>
-			<para>    Set(foo=${FILE(/tmp/test.txt,3,1,l)})</para>
-			<para>    ; reads the 3rd and 4th lines of the file.</para>
-			<para>    Set(foo=${FILE(/tmp/test.txt,3,2,l)})</para>
-			<para>    ; reads from the third line to the end of the file.</para>
-			<para>    Set(foo=${FILE(/tmp/test.txt,3,,l)})</para>
-			<para>    ; reads the last three lines of the file.</para>
-			<para>    Set(foo=${FILE(/tmp/test.txt,-3,,l)})</para>
-			<para>    ; reads the 3rd line of a DOS-formatted file.</para>
-			<para>    Set(foo=${FILE(/tmp/test.txt,3,1,l,d)})</para>
-			<para/>
+			<example title="Reads the 3rd line of the file">
+			same => n,Set(foo=${FILE(/tmp/test.txt,3,1,l)})
+			</example>
+			<example title="Reads the 3rd and 4th lines of the file">
+			same => n,Set(foo=${FILE(/tmp/test.txt,3,2,l)})
+			</example>
+			<example title="Reads from the third line to the end of the file">
+			same => n,Set(foo=${FILE(/tmp/test.txt,3,,l)})
+			</example>
+			<example title="Reads the last three lines of the file">
+			same => n,Set(foo=${FILE(/tmp/test.txt,-3,,l)})
+			</example>
+			<example title="Reads the 3rd line of a DOS-formatted file">
+			same => n,Set(foo=${FILE(/tmp/test.txt,3,1,l,d)})
+			</example>
 			<para>Write mode (byte):</para>
-			<para>    ; truncate the file and write "bar"</para>
-			<para>    Set(FILE(/tmp/test.txt)=bar)</para>
-			<para>    ; Append "bar"</para>
-			<para>    Set(FILE(/tmp/test.txt,,,a)=bar)</para>
-			<para>    ; Replace the first byte with "bar" (replaces 1 character with 3)</para>
-			<para>    Set(FILE(/tmp/test.txt,0,1)=bar)</para>
-			<para>    ; Replace 10 bytes beginning at the 21st byte of the file with "bar"</para>
-			<para>    Set(FILE(/tmp/test.txt,20,10)=bar)</para>
-			<para>    ; Replace all bytes from the 21st with "bar"</para>
-			<para>    Set(FILE(/tmp/test.txt,20)=bar)</para>
-			<para>    ; Insert "bar" after the 4th character</para>
-			<para>    Set(FILE(/tmp/test.txt,4,0)=bar)</para>
-			<para/>
+			<example title="Truncate the file and write bar">
+			same => n,Set(FILE(/tmp/test.txt)=bar)
+			</example>
+			<example title="Append bar">
+			same => n,Set(FILE(/tmp/test.txt,,,a)=bar)
+			</example>
+			<example title="Replace the first byte with bar (replaces 1 character with 3)">
+			same => n,Set(FILE(/tmp/test.txt,0,1)=bar)
+			</example>
+			<example title="Replace 10 bytes beginning at the 21st byte of the file with bar">
+			same => n,Set(FILE(/tmp/test.txt,20,10)=bar)
+			</example>
+			<example title="Replace all bytes from the 21st with bar">
+			same => n,Set(FILE(/tmp/test.txt,20)=bar)
+			</example>
+			<example title="Insert bar after the 4th character">
+			same => n,Set(FILE(/tmp/test.txt,4,0)=bar)
+			</example>
 			<para>Write mode (line):</para>
-			<para>    ; Replace the first line of the file with "bar"</para>
-			<para>    Set(FILE(/tmp/foo.txt,0,1,l)=bar)</para>
-			<para>    ; Replace the last line of the file with "bar"</para>
-			<para>    Set(FILE(/tmp/foo.txt,-1,,l)=bar)</para>
-			<para>    ; Append "bar" to the file with a newline</para>
-			<para>    Set(FILE(/tmp/foo.txt,,,al)=bar)</para>
+			<example title="Replace the first line of the file with bar">
+			same => n,Set(FILE(/tmp/foo.txt,0,1,l)=bar)
+			</example>
+			<example title="Replace the last line of the file with bar">
+			same => n,Set(FILE(/tmp/foo.txt,-1,,l)=bar)
+			</example>
+			<example title="Append bar to the file with a newline">
+			same => n,Set(FILE(/tmp/foo.txt,,,al)=bar)
+			</example>
 			<note>
 				<para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
 				is set to <literal>no</literal>, this function can only be executed from the
@@ -291,6 +304,11 @@
 		</see-also>
 	</function>
 	<function name="BASENAME" language="en_US">
+		<since>
+			<version>16.21.0</version>
+			<version>18.7.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Return the name of a file.
 		</synopsis>
@@ -309,6 +327,11 @@
 		</see-also>
 	</function>
 	<function name="DIRNAME" language="en_US">
+		<since>
+			<version>16.21.0</version>
+			<version>18.7.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Return the directory of a file.
 		</synopsis>
diff --git a/funcs/func_evalexten.c b/funcs/func_evalexten.c
new file mode 100644
index 0000000000000000000000000000000000000000..6a7d28bc902c78ef377f58b5f87cf17d4b70bb62
--- /dev/null
+++ b/funcs/func_evalexten.c
@@ -0,0 +1,147 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Naveen Albert
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Dialplan extension evaluation function
+ *
+ * \author Naveen Albert <asterisk@phreaknet.org>
+ *
+ * \ingroup functions
+ */
+
+/*** MODULEINFO
+	<support_level>extended</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+#include "asterisk/app.h"
+
+/*** DOCUMENTATION
+	<function name="EVAL_EXTEN" language="en_US">
+		<synopsis>
+			Evaluates the contents of a dialplan extension and returns it as a string.
+		</synopsis>
+		<syntax>
+			<parameter name="context" />
+			<parameter name="extensions" />
+			<parameter name="priority" required="true" />
+		</syntax>
+		<description>
+			<para>The EVAL_EXTEN function looks up a dialplan entry by context,extension,priority,
+			evaluates the contents of a Return statement to resolve any variable or function
+			references, and returns the result as a string.</para>
+			<para>You can use this function to create simple user-defined lookup tables or
+			user-defined functions.</para>
+			<example title="Custom dialplan functions">
+			[call-types]
+			exten => _1NNN,1,Return(internal)
+			exten => _NXXNXXXXXX,1,Return(external)
+
+			[udf]
+			exten => calleridlen,1,Return(${LEN(${CALLERID(num)})})
+
+			[default]
+			exten => _X!,1,Verbose(Call type ${EVAL_EXTEN(call-types,${EXTEN},1)} - ${EVAL_EXTEN(udf,calleridlen,1)})
+			</example>
+			<para>Any variables in the evaluated data will be resolved in the context of
+			that extension. For example, <literal>${EXTEN}</literal> would refer to the
+			EVAL_EXTEN extension, not the extension in the context invoking the function.
+			This behavior is similar to other applications, e.g. <literal>Gosub</literal>.</para>
+			<example title="Choosing which prompt to use">
+			same => n,Read(input,${EVAL_EXTEN(prompts,${CALLERID(num)},1)})
+
+			[prompts]
+			exten => _X!,1,Return(default)
+			exten => _20X,1,Return(welcome)
+			exten => _2XX,1,Return(${DB(promptsettings/${EXTEN})})
+			exten => _3XX,1,Return(${ODBC_MYFUNC(${EXTEN})})
+			</example>
+			<para>Extensions on which EVAL_EXTEN is invoked are not different from other
+			extensions. However, the application at that extension is not executed.
+			Only the application data is parsed and evaluated.</para>
+			<para>A limitation of this function is that the application at the specified
+			extension isn't actually executed, and thus unlike a Gosub, you can't pass
+			arguments in the EVAL_EXTEN function.</para>
+		</description>
+		<see-also>
+			<ref type="function">EVAL</ref>
+		</see-also>
+	</function>
+ ***/
+
+static int eval_exten_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+	char *exten, *pri, *context, *parse;
+	int ipri;
+	char tmpbuf[len];
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "The EVAL_EXTEN function requires an extension\n");
+		return -1;
+	}
+
+	parse = ast_strdupa(data);
+	/* Split context,exten,pri */
+	context = strsep(&parse, ",");
+	exten = strsep(&parse, ",");
+	pri = strsep(&parse, ",");
+
+	if (pbx_parse_location(chan, &context, &exten, &pri, &ipri, NULL, NULL)) {
+		return -1;
+	}
+
+	if (ast_strlen_zero(exten) || ast_strlen_zero(context)) { /* only lock if we really need to */
+		ast_channel_lock(chan);
+		if (ast_strlen_zero(exten)) {
+			exten = ast_strdupa(ast_channel_exten(chan));
+		}
+		if (ast_strlen_zero(context)) {
+			context = ast_strdupa(ast_channel_context(chan));
+		}
+		ast_channel_unlock(chan);
+	}
+
+	if (ast_get_extension_data(tmpbuf, len, chan, context, exten, ipri)) {
+		return -1; /* EVAL_EXTEN failed */
+	}
+
+	pbx_substitute_variables_helper_full_location(chan, (chan) ? ast_channel_varshead(chan) : NULL, tmpbuf, buf, len, NULL, context, exten, ipri);
+
+	return 0;
+}
+
+static struct ast_custom_function eval_exten_function = {
+	.name = "EVAL_EXTEN",
+	.read = eval_exten_read,
+};
+
+static int unload_module(void)
+{
+	return ast_custom_function_unregister(&eval_exten_function);
+}
+
+static int load_module(void)
+{
+	return ast_custom_function_register(&eval_exten_function);
+}
+
+AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Extension evaluation function");
diff --git a/funcs/func_export.c b/funcs/func_export.c
new file mode 100644
index 0000000000000000000000000000000000000000..e231b641072ef0a9fb5ce854a750c0ee32723f07
--- /dev/null
+++ b/funcs/func_export.c
@@ -0,0 +1,107 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021-2022, Naveen Albert
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Set variables and functions on other channels
+ *
+ * \author Naveen Albert <asterisk@phreaknet.org>
+ *
+ * \ingroup functions
+ */
+
+/*** MODULEINFO
+	<support_level>extended</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+#include "asterisk/app.h"
+#include "asterisk/stringfields.h"
+
+/*** DOCUMENTATION
+	<function name="EXPORT" language="en_US">
+		<synopsis>
+			Set variables or dialplan functions on any arbitrary channel that exists.
+		</synopsis>
+		<syntax>
+			<parameter name="channel" required="true">
+				<para>The complete channel name: <literal>SIP/12-abcd1234</literal>.</para>
+			</parameter>
+			<parameter name="var" required="true">
+				<para>Variable name</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Allows setting variables or functions on any existing channel if it exists.</para>
+		</description>
+		<see-also>
+			<ref type="function">IMPORT</ref>
+			<ref type="function">MASTER_CHANNEL</ref>
+			<ref type="function">SHARED</ref>
+		</see-also>
+	</function>
+ ***/
+
+static int func_export_write(struct ast_channel *chan, const char *function, char *data, const char *value)
+{
+	struct ast_channel *ochan;
+
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(channel);
+		AST_APP_ARG(var);
+	);
+	AST_STANDARD_APP_ARGS(args, data);
+
+	if (!args.channel) {
+		ast_log(LOG_WARNING, "No channel was provided to %s function.\n", function);
+		return -1;
+	}
+	if (!args.var) {
+		ast_log(LOG_WARNING, "No variable name was provided to %s function.\n", function);
+		return -1;
+	}
+	ochan = ast_channel_get_by_name(args.channel);
+	if (!ochan) {
+		ast_log(LOG_WARNING, "Channel '%s' not found! '%s' not set.\n", args.channel, args.var);
+		return -1;
+	}
+
+	pbx_builtin_setvar_helper(ochan, data, value);
+	ast_channel_unref(ochan);
+	return 0;
+}
+
+static struct ast_custom_function export_function = {
+	.name = "EXPORT",
+	.write = func_export_write,
+};
+
+static int unload_module(void)
+{
+	return ast_custom_function_unregister(&export_function);
+}
+
+static int load_module(void)
+{
+	return ast_custom_function_register(&export_function);
+}
+
+AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Set variables and functions on other channels");
diff --git a/funcs/func_frame_drop.c b/funcs/func_frame_drop.c
index c19c56680c6df9dc2dcbefff3cfa507e52d08ea1..522685cbd07e1a1ff61b80ea7dbaa7f3ebed8d79 100644
--- a/funcs/func_frame_drop.c
+++ b/funcs/func_frame_drop.c
@@ -38,6 +38,11 @@
 
 /*** DOCUMENTATION
 	<function name="FRAME_DROP" language="en_US">
+		<since>
+			<version>16.21.0</version>
+			<version>18.7.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Drops specific frame types in the TX or RX direction on a channel.
 		</synopsis>
@@ -86,9 +91,15 @@
 		</syntax>
 		<description>
 			<para>Examples:</para>
-			<para>exten => 1,1,Set(FRAME_DROP(TX)=DTMF_BEGIN,DTMF_END); drop only DTMF frames towards this channel.</para>
-			<para>exten => 1,1,Set(FRAME_DROP(TX)=ANSWER); drop only ANSWER CONTROL frames towards this channel.</para>
-			<para>exten => 1,1,Set(FRAME_DROP(RX)=DTMF_BEGIN,DTMF_END); drop only DTMF frames received on this channel.</para>
+			<example title="Drop only DTMF frames towards this channel">
+			exten => 1,1,Set(FRAME_DROP(TX)=DTMF_BEGIN,DTMF_END)
+			</example>
+			<example title="Drop only Answer control frames towards this channel">
+			exten => 1,1,Set(FRAME_DROP(TX)=ANSWER)
+			</example>
+			<example title="Drop only DTMF frames received on this channel">
+			exten => 1,1,Set(FRAME_DROP(RX)=DTMF_BEGIN,DTMF_END)
+			</example>
 		</description>
 	</function>
  ***/
diff --git a/funcs/func_frame_trace.c b/funcs/func_frame_trace.c
index b62bae9deeb4421d5bf1a0f83a7da4ba3ca994a1..108f17aa86948e0cc910b5b2363a57eaace2df45 100644
--- a/funcs/func_frame_trace.c
+++ b/funcs/func_frame_trace.c
@@ -69,9 +69,15 @@
 		</syntax>
 		<description>
 			<para>Examples:</para>
-			<para>exten => 1,1,Set(FRAME_TRACE(white)=DTMF_BEGIN,DTMF_END); view only DTMF frames. </para>
-			<para>exten => 1,1,Set(FRAME_TRACE()=DTMF_BEGIN,DTMF_END); view only DTMF frames. </para>
-			<para>exten => 1,1,Set(FRAME_TRACE(black)=DTMF_BEGIN,DTMF_END); view everything except DTMF frames. </para>
+			<example title="View only DTMF frames">
+			exten => 1,1,Set(FRAME_TRACE(white)=DTMF_BEGIN,DTMF_END)
+			</example>
+			<example title="View only DTMF frames">
+			exten => 1,1,Set(FRAME_TRACE()=DTMF_BEGIN,DTMF_END)
+			</example>
+			<example title="View everything except DTMF frames">
+			exten => 1,1,Set(FRAME_TRACE(black)=DTMF_BEGIN,DTMF_END)
+			</example>
 		</description>
 	</function>
  ***/
@@ -335,8 +341,7 @@ static void print_frame(struct ast_frame *frame)
 			ast_verbose("SubClass: PVT_CAUSE_CODE\n");
 			break;
 		case AST_CONTROL_MASQUERADE_NOTIFY:
-			/* Should never happen. */
-			ast_assert(0);
+			ast_verbose("SubClass: MASQUERADE_NOTIFY\n");
 			break;
 		case AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE:
 			ast_verbose("SubClass: STREAM_TOPOLOGY_REQUEST_CHANGE\n");
@@ -392,6 +397,7 @@ static void print_frame(struct ast_frame *frame)
 		break;
 	case AST_FRAME_TEXT:
 		ast_verbose("FrameType: TXT\n");
+		ast_verbose("Text: %.*s\n", frame->datalen, (char*) frame->data.ptr);
 		break;
 	case AST_FRAME_TEXT_DATA:
 		ast_verbose("FrameType: TXT_DATA\n");
diff --git a/funcs/func_json.c b/funcs/func_json.c
index a48b85c663f94d8c82943d9f3e2d09f439740af8..8bfcd4cfb170093585fde0aaf47f856ee6e43531 100644
--- a/funcs/func_json.c
+++ b/funcs/func_json.c
@@ -1,7 +1,7 @@
 /*
  * Asterisk -- An open source telephony toolkit.
  *
- * Copyright (C) 2021, Naveen Albert
+ * Copyright (C) 2021-2022, Naveen Albert
  *
  * See http://www.asterisk.org for more information about
  * the Asterisk project. Please do not directly contact
@@ -39,6 +39,11 @@
 
 /*** DOCUMENTATION
 	<function name="JSON_DECODE" language="en_US">
+		<since>
+			<version>16.24.0</version>
+			<version>18.10.0</version>
+			<version>19.2.0</version>
+		</since>
 		<synopsis>
 			Returns the string value of a JSON object key from a string containing a
 			JSON array.
@@ -49,6 +54,21 @@
 			</parameter>
 			<parameter name="item" required="true">
 				<para>The name of the key whose value to return.</para>
+				<para>Multiple keys can be listed separated by a hierarchy delimeter, which will recursively index into a nested JSON string to retrieve a specific subkey's value.</para>
+			</parameter>
+			<parameter name="separator" required="false">
+				<para>A single character that delimits a key hierarchy for nested indexing. Default is a period (.)</para>
+				<para>This value should not appear in the key or hierarchy of keys itself, except to delimit the hierarchy of keys.</para>
+			</parameter>
+			<parameter name="options" required="no">
+				<optionlist>
+					<option name="c">
+						<para>For keys that reference a JSON array, return
+						the number of items in the array.</para>
+						<para>This option has no effect on any other type
+						of value.</para>
+					</option>
+				</optionlist>
 			</parameter>
 		</syntax>
 		<description>
@@ -64,19 +84,135 @@
 
 AST_THREADSTORAGE(result_buf);
 
+enum json_option_flags {
+	OPT_COUNT = (1 << 0),
+};
+
+AST_APP_OPTIONS(json_options, {
+	AST_APP_OPTION('c', OPT_COUNT),
+});
+
+#define MAX_JSON_STACK 32
+
+static int parse_node(char **key, char *currentkey, char *nestchar, int count, struct ast_json *json, char *buf, size_t len, int *depth)
+{
+	const char *result = NULL;
+	char *previouskey;
+	struct ast_json *jsonval = json;
+
+	/* Prevent a huge JSON string from blowing the stack. */
+	if (*depth > MAX_JSON_STACK) {
+		ast_log(LOG_WARNING, "Max JSON stack (%d) exceeded\n", MAX_JSON_STACK);
+		return -1;
+	}
+
+	snprintf(buf, len, "%s", ""); /* clear the buffer from previous round if necessary */
+	if (!json) { /* no error or warning should be thrown */
+		ast_debug(1, "Could not find key '%s' in parsed JSON\n", currentkey);
+		return -1;
+	}
+
+	switch(ast_json_typeof(jsonval)) {
+		unsigned long int size;
+		int r;
+
+		case AST_JSON_STRING:
+			result = ast_json_string_get(jsonval);
+			ast_debug(1, "Got JSON string: %s\n", result);
+			ast_copy_string(buf, result, len);
+			break;
+		case AST_JSON_INTEGER:
+			r = ast_json_integer_get(jsonval);
+			ast_debug(1, "Got JSON integer: %d\n", r);
+			snprintf(buf, len, "%d", r); /* the snprintf below is mutually exclusive with this one */
+			break;
+		case AST_JSON_ARRAY:
+			ast_debug(1, "Got JSON array\n");
+			previouskey = currentkey;
+			currentkey = strsep(key, nestchar); /* retrieve the desired index */
+			size = ast_json_array_size(jsonval);
+			ast_debug(1, "Parsed JSON array of size %lu, key: %s\n", size, currentkey);
+			if (!currentkey) { /* this is the end, so just dump the array */
+				if (count) {
+					ast_debug(1, "No key on which to index in the array, so returning count: %lu\n", size);
+					snprintf(buf, len, "%lu", size);
+					return 0;
+				} else {
+					char *result2 = ast_json_dump_string(jsonval);
+					ast_debug(1, "No key on which to index in the array, so dumping '%s' array\n", previouskey);
+					ast_copy_string(buf, result2, len);
+					ast_json_free(result2);
+				}
+			} else if (ast_str_to_int(currentkey, &r) || r < 0) {
+				ast_debug(1, "Requested index '%s' is not numeric or is invalid\n", currentkey);
+			} else if (r >= size) {
+				ast_debug(1, "Requested index '%d' does not exist in parsed array\n", r);
+			} else {
+				struct ast_json *json2 = ast_json_array_get(jsonval, r);
+				if (!json2) {
+					ast_debug(1, "Array index %d contains empty item\n", r);
+					return -1;
+				}
+				previouskey = currentkey;
+				currentkey = strsep(key, nestchar); /* get the next subkey */
+				ast_debug(1, "Recursing on index %d in array (key was '%s' and is now '%s')\n", r, previouskey, currentkey);
+				/* json2 is a borrowed ref. That's fine, since json won't get freed until recursing is over */
+				/* If there are keys remaining, then parse the next object we can get. Otherwise, just dump the child */
+				if (parse_node(key, currentkey, nestchar, count, currentkey ? ast_json_object_get(json2, currentkey) : json2, buf, len, depth)) { /* recurse on this node */
+					return -1;
+				}
+			}
+			break;
+		case AST_JSON_OBJECT:
+		default:
+			ast_debug(1, "Got generic JSON object for key %s\n", currentkey);
+			if (!currentkey) { /* this is the end, so just dump the object */
+				char *result2 = ast_json_dump_string(jsonval);
+				ast_copy_string(buf, result2, len);
+				ast_json_free(result2);
+			} else {
+				previouskey = currentkey;
+				currentkey = strsep(key, nestchar); /* retrieve the desired index */
+				ast_debug(1, "Recursing on object (key was '%s' and is now '%s')\n", previouskey, currentkey);
+				if (!currentkey) { /* this is the end, so just dump the object */
+					char *result2 = ast_json_dump_string(jsonval);
+					ast_copy_string(buf, result2, len);
+					ast_json_free(result2);
+				} else if (parse_node(key, currentkey, nestchar, count, ast_json_object_get(jsonval, currentkey), buf, len, depth)) { /* recurse on this node */
+					return -1;
+				}
+			}
+			break;
+	}
+	return 0;
+}
+
 static int json_decode_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
 {
-	struct ast_json *json, *jsonval;
+	int count = 0;
+	struct ast_flags flags = {0};
+	struct ast_json *json = NULL;
+	char *nestchar = "."; /* default delimeter for nesting key indexing is . */
+	int res, depth = 0;
+
 	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(varname);
 		AST_APP_ARG(key);
+		AST_APP_ARG(nestchar);
+		AST_APP_ARG(options);
 	);
-	char *varsubst, *result2;
-	const char *result = NULL;
+	char *varsubst, *key, *currentkey, *nextkey, *firstkey, *tmp;
 	struct ast_str *str = ast_str_thread_get(&result_buf, 16);
 
 	AST_STANDARD_APP_ARGS(args, data);
 
+	if (!ast_strlen_zero(args.options)) {
+		ast_app_parse_options(json_options, &flags, NULL, args.options);
+		if (ast_test_flag(&flags, OPT_COUNT)) {
+			count = 1;
+		}
+	}
+
 	if (ast_strlen_zero(args.varname)) {
 		ast_log(LOG_WARNING, "%s requires a variable name\n", cmd);
 		return -1;
@@ -85,6 +221,15 @@ static int json_decode_read(struct ast_channel *chan, const char *cmd, char *dat
 		ast_log(LOG_WARNING, "%s requires a key\n", cmd);
 		return -1;
 	}
+	key = ast_strdupa(args.key);
+	if (!ast_strlen_zero(args.nestchar)) {
+		int seplen = strlen(args.nestchar);
+		if (seplen != 1) {
+			ast_log(LOG_WARNING, "Nesting separator '%s' has length %d and is invalid (must be a single character)\n", args.nestchar, seplen);
+		} else {
+			nestchar = args.nestchar;
+		}
+	}
 
 	varsubst = ast_alloca(strlen(args.varname) + 4); /* +4 for ${} and null terminator */
 	if (!varsubst) {
@@ -93,45 +238,43 @@ static int json_decode_read(struct ast_channel *chan, const char *cmd, char *dat
 	}
 	sprintf(varsubst, "${%s}", args.varname); /* safe, because of the above allocation */
 	ast_str_substitute_variables(&str, 0, chan, varsubst);
+
+	ast_debug(1, "Parsing JSON using nesting delimeter '%s'\n", nestchar);
+
 	if (ast_str_strlen(str) == 0) {
 		ast_debug(1, "Variable '%s' contains no data, nothing to search!\n", args.varname);
 		return -1; /* empty json string */
 	}
 
-	ast_debug(1, "Parsing JSON: %s\n", ast_str_buffer(str));
+	/* allow for multiple key nesting */
+	currentkey = key;
+	firstkey = ast_strdupa(currentkey);
+	tmp = strstr(firstkey, nestchar);
+	if (tmp) {
+		*tmp = '\0';
+	}
 
+	/* parse a string as JSON */
+	ast_debug(1, "Parsing JSON: %s (key: '%s')\n", ast_str_buffer(str), currentkey);
+	if (ast_strlen_zero(currentkey)) {
+		ast_debug(1, "Empty JSON key\n");
+		return -1;
+	}
+	if (ast_str_strlen(str) == 0) {
+		ast_debug(1, "JSON node '%s', contains no data, nothing to search!\n", currentkey);
+		return -1; /* empty json string */
+	}
 	json = ast_json_load_str(str, NULL);
-
 	if (!json) {
 		ast_log(LOG_WARNING, "Failed to parse as JSON: %s\n", ast_str_buffer(str));
 		return -1;
 	}
 
-	jsonval = ast_json_object_get(json, args.key);
-	if (!jsonval) { /* no error or warning should be thrown */
-		ast_debug(1, "Could not find key '%s' in parsed JSON\n", args.key);
-		ast_json_free(json);
-		return -1;
-	}
-	switch(ast_json_typeof(jsonval)) {
-		int r;
-		case AST_JSON_STRING:
-			result = ast_json_string_get(jsonval);
-			snprintf(buf, len, "%s", result);
-			break;
-		case AST_JSON_INTEGER:
-			r = ast_json_integer_get(jsonval);
-			snprintf(buf, len, "%d", r); /* the snprintf below is mutually exclusive with this one */
-			break;
-		default:
-			result2 = ast_json_dump_string(jsonval);
-			snprintf(buf, len, "%s", result2);
-			ast_json_free(result2);
-			break;
-	}
-	ast_json_free(json);
-
-	return 0;
+	/* parse the JSON object, potentially recursively */
+	nextkey = strsep(&key, nestchar);
+	res = parse_node(&key, nextkey, nestchar, count, ast_json_object_get(json, firstkey), buf, len, &depth);
+	ast_json_unref(json);
+	return res;
 }
 
 static struct ast_custom_function json_decode_function = {
@@ -146,12 +289,27 @@ AST_TEST_DEFINE(test_JSON_DECODE)
 	struct ast_channel *chan; /* dummy channel */
 	struct ast_str *str; /* fancy string for holding comparing value */
 
-	const char *test_strings[][5] = {
-		{"{\"city\": \"Anytown\", \"state\": \"USA\"}", "city", "Anytown"},
-		{"{\"city\": \"Anytown\", \"state\": \"USA\"}", "state", "USA"},
-		{"{\"city\": \"Anytown\", \"state\": \"USA\"}", "blah", ""},
-		{"{\"key1\": \"123\", \"key2\": \"456\"}", "key1", "123"},
-		{"{\"key1\": 123, \"key2\": 456}", "key1", "123"},
+	const char *test_strings[][6] = {
+		{"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "city", "Anytown"},
+		{"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "state", "USA"},
+		{"{\"city\": \"Anytown\", \"state\": \"USA\"}", "", "blah", ""},
+		{"{\"key1\": \"123\", \"key2\": \"456\"}", "", "key1", "123"},
+		{"{\"key1\": 123, \"key2\": 456}", "", "key1", "123"},
+		{"{ \"path\": { \"to\": { \"elem\": \"someVar\" } } }", "/", "path/to/elem", "someVar"},
+		{"{ \"path\": { \"to\": { \"elem\": \"someVar\" } } }", "", "path.to.elem2", ""},
+		{"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr/2", ""}, /* nonexistent index */
+		{"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr/-1", ""}, /* bogus index */
+		{"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr/test", ""}, /* bogus index */
+		{"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "", "path.to.arr.test.test2.subkey", ""}, /* bogus index */
+		{"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", ",c", "path.to.arr", "2"}, /* test count */
+		{"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "", "path.to.arr", "[\"item0\",\"item1\"]"},
+		{"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", ".", "path.to.arr.1", "item1"},
+		{"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr", "[\"item0\",\"item1\"]"},
+		{"{ \"path\": { \"to\": { \"arr\": [ \"item0\", \"item1\" ] } } }", "/", "path/to/arr/1", "item1"},
+		{"{ \"path\": { \"to\": { \"arr\": [ {\"name\": \"John Smith\", \"phone\": \"123\"}, {\"name\": \"Jane Doe\", \"phone\": \"234\"} ] } } }", ",c", "path.to.arr.0.name", "John Smith"},
+		{"{ \"path\": { \"to\": { \"arr\": [ {\"name\": 1, \"phone\": 123}, {\"name\": 2, \"phone\": 234} ] } } }", ",c", "path.to.arr.0.name", "1"},
+		{"{ \"path\": { \"to\": { \"arr\": [ {\"name\": [ \"item11\", \"item12\" ], \"phone\": [ \"item13\", \"item14\" ]}, {\"name\": [ \"item15\", \"item16\" ], \"phone\": [ \"item17\", \"item18\" ]} ] } } }", ",c", "path.to.arr.0.name.1", "item12"},
+		{"{ \"startId\": \"foobar\", \"abcd\": { \"id\": \"abcd\", \"type\": \"EXT\" }, \"bcde\": { \"id\": \"bcde\", \"type\": \"CONDITION\" }, \"defg\": { \"id\": \"defg\", \"type\": \"EXT\" }, \"efgh\": { \"id\": \"efgh\", \"type\": \"VOICEMAIL\" } }", "", "bcde", "{\"id\":\"bcde\",\"type\":\"CONDITION\"}"},
 	};
 
 	switch (cmd) {
@@ -177,7 +335,7 @@ AST_TEST_DEFINE(test_JSON_DECODE)
 	}
 
 	for (i = 0; i < ARRAY_LEN(test_strings); i++) {
-		char tmp[512], tmp2[512] = "";
+		char tmp[512];
 
 		struct ast_var_t *var = ast_var_assign("test_string", test_strings[i][0]);
 		if (!var) {
@@ -189,11 +347,11 @@ AST_TEST_DEFINE(test_JSON_DECODE)
 
 		AST_LIST_INSERT_HEAD(ast_channel_varshead(chan), var, entries);
 
-		snprintf(tmp, sizeof(tmp), "${JSON_DECODE(%s,%s)}", "test_string", test_strings[i][1]);
+		snprintf(tmp, sizeof(tmp), "${JSON_DECODE(%s,%s,%s)}", "test_string", test_strings[i][2], test_strings[i][1]);
 
 		ast_str_substitute_variables(&str, 0, chan, tmp);
-		if (strcmp(test_strings[i][2], ast_str_buffer(str))) {
-			ast_test_status_update(test, "Format string '%s' substituted to '%s'.  Expected '%s'.\n", test_strings[i][0], tmp2, test_strings[i][2]);
+		if (strcmp(test_strings[i][3], ast_str_buffer(str))) {
+			ast_test_status_update(test, "Format string '%s' substituted to '%s' (key: %s). Expected '%s'.\n", test_strings[i][0], ast_str_buffer(str), test_strings[i][2], test_strings[i][3]);
 			res = AST_TEST_FAIL;
 		}
 	}
@@ -209,7 +367,9 @@ static int unload_module(void)
 {
 	int res;
 
+#ifdef TEST_FRAMEWORK
 	AST_TEST_UNREGISTER(test_JSON_DECODE);
+#endif
 	res = ast_custom_function_unregister(&json_decode_function);
 
 	return res;
@@ -219,7 +379,9 @@ static int load_module(void)
 {
 	int res;
 
+#ifdef TEST_FRAMEWORK
 	AST_TEST_REGISTER(test_JSON_DECODE);
+#endif
 	res = ast_custom_function_register(&json_decode_function);
 
 	return res;
diff --git a/funcs/func_logic.c b/funcs/func_logic.c
index d2677493334a305e3c8670c6a3809e76ef93100f..b1411f2f19b566d7007da37b4db2de9d08570de3 100644
--- a/funcs/func_logic.c
+++ b/funcs/func_logic.c
@@ -72,10 +72,10 @@
 	</function>
 	<function name="IF" language="en_US">
 		<synopsis>
-			Check for an expresion.
+			Check for an expression.
 		</synopsis>
 		<syntax argsep="?">
-			<parameter name="expresion" required="true" />
+			<parameter name="expression" required="true" />
 			<parameter name="retvalue" argsep=":" required="true">
 				<argument name="true" />
 				<argument name="false" />
@@ -187,8 +187,7 @@ static int acf_if(struct ast_channel *chan, const char *cmd, char *data, char *b
 	AST_NONSTANDARD_APP_ARGS(args2, args1.remainder, ':');
 
 	if (ast_strlen_zero(args1.expr) || !(args2.iftrue || args2.iffalse)) {
-		ast_log(LOG_WARNING, "Syntax IF(<expr>?[<true>][:<false>])  (expr must be non-null, and either <true> or <false> must be non-null)\n");
-		ast_log(LOG_WARNING, "      In this case, <expr>='%s', <true>='%s', and <false>='%s'\n", args1.expr, args2.iftrue, args2.iffalse);
+		ast_debug(1, "<expr>='%s', <true>='%s', and <false>='%s'\n", args1.expr, args2.iftrue, args2.iffalse);
 		return -1;
 	}
 
diff --git a/funcs/func_math.c b/funcs/func_math.c
index 76d1a745fe4f6982e1715c61ee3fc59e00ca26f5..c3bc71fbf446f80389db2cfa2952c2d7532a0f1e 100644
--- a/funcs/func_math.c
+++ b/funcs/func_math.c
@@ -71,7 +71,9 @@
 		<description>
 			<para>Performs mathematical functions based on two parameters and an operator.  The returned
 			value type is <replaceable>type</replaceable></para>
-			<para>Example: Set(i=${MATH(123%16,int)}) - sets var i=11</para>
+			<example title="Sets var i to 11">
+			same => n,Set(i=${MATH(123%16,int)})
+			</example>
 		</description>
 	</function>
 	<function name="INC" language="en_US">
@@ -104,11 +106,18 @@
 		</syntax>
 		<description>
 			<para>Decrements the value of a variable, while returning the updated value to the dialplan</para>
-			<para>Example: DEC(MyVAR) - Decrements MyVar</para>
-			<para>Note: DEC(${MyVAR}) - Is wrong, as DEC expects the variable name, not its value</para>
+			<example title="Decrements MyVAR">
+			same => n,NoOp(${DEC(MyVAR)})
+			</example>
+			<note><para>DEC(${MyVAR}) is wrong, as DEC expects the variable name, not its value</para></note>
 		</description>
 	</function>
 	<function name="MIN" language="en_US">
+		<since>
+			<version>16.19.0</version>
+			<version>18.5.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Returns the minimum of two numbers.
 		</synopsis>
@@ -118,11 +127,17 @@
 		</syntax>
 		<description>
 			<para>Returns the minimum of two numbers <replaceable>num1</replaceable> and <replaceable>num2</replaceable>.</para>
-			<para>Example:  Set(min=${MIN(7,4)});
-			Sets the min variable equal to 4.</para>
+			<example title="Sets the min variable equal to 4">
+			same => n,Set(min=${MIN(7,4)})
+			</example>
 		</description>
 	</function>
 	<function name="MAX" language="en_US">
+		<since>
+			<version>16.19.0</version>
+			<version>18.5.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Returns the maximum of two numbers.
 		</synopsis>
@@ -132,11 +147,17 @@
 		</syntax>
 		<description>
 			<para>Returns the maximum of two numbers <replaceable>num1</replaceable> and <replaceable>num2</replaceable>.</para>
-			<para>Example:  Set(max=${MAX(4,7)});
-			Sets the max variable equal to 7.</para>
+			<example title="Sets the max variable equal to 13">
+			same => n,Set(max=${MAX(4,7)})
+			</example>
 		</description>
 	</function>
 	<function name="ABS" language="en_US">
+		<since>
+			<version>16.19.0</version>
+			<version>18.5.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Returns absolute value of a number.
 		</synopsis>
@@ -145,8 +166,9 @@
 		</syntax>
 		<description>
 			<para>Returns the absolute value of a number <replaceable>num</replaceable>.</para>
-			<para>Example:  Set(absval=${ABS(-13)});
-			Sets the absval variable equal to 13.</para>
+			<example title="Sets the absval variable equal to 13">
+			same => n,Set(absval=${ABS(-13)})
+			</example>
 		</description>
 	</function>
  ***/
diff --git a/funcs/func_odbc.c b/funcs/func_odbc.c
index 7e4e6a3bb78706abf448d887d77c8f6cc379ec32..43c4a0c6a5b0b166a9d5303847d9c9683df7bb59 100644
--- a/funcs/func_odbc.c
+++ b/funcs/func_odbc.c
@@ -93,7 +93,9 @@
 		<description>
 			<para>Used in SQL templates to escape data which may contain single ticks
 			<literal>'</literal> which are otherwise used to delimit data.</para>
-			<para>Example: SELECT foo FROM bar WHERE baz='${SQL_ESC(${ARG1})}'</para>
+			<example title="Escape example">
+			 SELECT foo FROM bar WHERE baz='${SQL_ESC(${ARG1})}'
+			</example>
 		</description>
 	</function>
 	<function name="SQL_ESC_BACKSLASHES" language="en_US">
@@ -106,7 +108,9 @@
 		<description>
 			<para>Used in SQL templates to escape data which may contain backslashes
 			<literal>\</literal> which are otherwise used to escape data.</para>
-			<para>Example: SELECT foo FROM bar WHERE baz='${SQL_ESC(${SQL_ESC_BACKSLASHES(${ARG1})})}'</para>
+			<example title="Escape with backslashes example">
+			SELECT foo FROM bar WHERE baz='${SQL_ESC(${SQL_ESC_BACKSLASHES(${ARG1})})}'
+			</example>
 		</description>
 	</function>
  ***/
diff --git a/funcs/func_periodic_hook.c b/funcs/func_periodic_hook.c
index 6b90ee3b83f0b6367eb57bb2b78933ac4a948e80..ec4b9ba4de9392b1012b564340e9d8095a63ed76 100644
--- a/funcs/func_periodic_hook.c
+++ b/funcs/func_periodic_hook.c
@@ -40,6 +40,7 @@
 #include "asterisk/pbx.h"
 #include "asterisk/app.h"
 #include "asterisk/audiohook.h"
+#include "asterisk/test.h"
 #define AST_API_MODULE
 #include "asterisk/beep.h"
 
@@ -67,15 +68,15 @@
 			<para>For example, you could use this function to enable playing
 			a periodic <literal>beep</literal> sound in a call.</para>
 			<para/>
-			<para>To turn on:</para>
-			<para>  Set(BEEPID=${PERIODIC_HOOK(hooks,beep,180)})</para>
-			<para/>
-			<para>To turn off:</para>
-			<para>  Set(PERIODIC_HOOK(${BEEPID})=off)</para>
-			<para/>
-			<para>To turn back on again later:</para>
-			<para>Set(PERIODIC_HOOK(${BEEPID})=on)</para>
-			<para/>
+			<example title="To turn on">
+			same => n,Set(BEEPID=${PERIODIC_HOOK(hooks,beep,180)})
+			</example>
+			<example title="To turn off">
+			same => n,Set(PERIODIC_HOOK(${BEEPID})=off)
+			</example>
+			<example title="To turn back on again later">
+			same => n,Set(PERIODIC_HOOK(${BEEPID})=on)
+			</example>
 			<para>It is important to note that the hook does not actually
 			run on the channel itself.  It runs asynchronously on a new channel.
 			Any audio generated by the hook gets injected into the call for
@@ -343,6 +344,8 @@ static int hook_on(struct ast_channel *chan, const char *data, unsigned int hook
 
 	ast_debug(1, "hook to %s@%s enabled on %s with interval of %u seconds\n",
 			args.exten, args.context, ast_channel_name(chan), interval);
+	ast_test_suite_event_notify("PERIODIC_HOOK_ENABLED", "Exten: %s\r\nChannel: %s\r\nInterval: %u\r\n",
+			args.exten, ast_channel_name(chan), interval);
 
 	return init_hook(chan, args.context, args.exten, interval, hook_id);
 }
diff --git a/funcs/func_pitchshift.c b/funcs/func_pitchshift.c
index 779f064e7267d6897377715cc4ede1c37d11aba9..4057ed9c874419e758c9d27f75e2516b0537a8a0 100644
--- a/funcs/func_pitchshift.c
+++ b/funcs/func_pitchshift.c
@@ -97,15 +97,30 @@
 		</syntax>
 		<description>
 			<para>Examples:</para>
-			<para>exten => 1,1,Set(PITCH_SHIFT(tx)=highest); raises pitch an octave </para>
-			<para>exten => 1,1,Set(PITCH_SHIFT(rx)=higher) ; raises pitch more </para>
-			<para>exten => 1,1,Set(PITCH_SHIFT(both)=high)   ; raises pitch </para>
-			<para>exten => 1,1,Set(PITCH_SHIFT(rx)=low)    ; lowers pitch </para>
-			<para>exten => 1,1,Set(PITCH_SHIFT(tx)=lower)  ; lowers pitch more </para>
-			<para>exten => 1,1,Set(PITCH_SHIFT(both)=lowest) ; lowers pitch an octave </para>
-
-			<para>exten => 1,1,Set(PITCH_SHIFT(rx)=0.8)    ; lowers pitch </para>
-			<para>exten => 1,1,Set(PITCH_SHIFT(tx)=1.5)    ; raises pitch </para>
+			<example title="Raises pitch an octave">
+			exten => 1,1,Set(PITCH_SHIFT(tx)=highest)
+			</example>
+			<example title="Raises pitch more">
+			exten => 1,1,Set(PITCH_SHIFT(rx)=higher)
+			</example>
+			<example title="Raises pitch">
+			exten => 1,1,Set(PITCH_SHIFT(both)=high)
+			</example>
+			<example title="Lowers pitch">
+			exten => 1,1,Set(PITCH_SHIFT(rx)=low)
+			</example>
+			<example title="Lowers pitch more">
+			exten => 1,1,Set(PITCH_SHIFT(tx)=lower)
+			</example>
+			<example title="Lowers pitch an octave">
+			exten => 1,1,Set(PITCH_SHIFT(both)=lowest)
+			</example>
+			<example title="Lowers pitch">
+			exten => 1,1,Set(PITCH_SHIFT(rx)=0.8)
+			</example>
+			<example title="Raises pitch">
+			exten => 1,1,Set(PITCH_SHIFT(tx)=1.5)
+			</example>
 		</description>
 	</function>
  ***/
diff --git a/funcs/func_presencestate.c b/funcs/func_presencestate.c
index 6005e48bb43b35c198dfeaa4aac144b303180105..8aba89121b5b372f49b8426426858e521a711fbb 100644
--- a/funcs/func_presencestate.c
+++ b/funcs/func_presencestate.c
@@ -850,8 +850,8 @@ static int load_module(void)
 	for (; db_entry; db_entry = db_entry->next) {
 		const char *dev_name = strrchr(db_entry->key, '/') + 1;
 		enum ast_presence_state state;
-		char *message;
-		char *subtype;
+		char *message = NULL;
+		char *subtype = NULL;
 		if (dev_name <= (const char *) 1) {
 			continue;
 		}
diff --git a/funcs/func_rand.c b/funcs/func_rand.c
index bf208e9e233d951638ff809699a8bf3687278c05..2b70ba34438fdab8b26bda8f1cb294f8e1cf102f 100644
--- a/funcs/func_rand.c
+++ b/funcs/func_rand.c
@@ -49,8 +49,9 @@
 			<para>Choose a random number between <replaceable>min</replaceable> and <replaceable>max</replaceable>.
 			<replaceable>min</replaceable> defaults to <literal>0</literal>, if not specified, while <replaceable>max</replaceable> defaults
 			to <literal>RAND_MAX</literal> (2147483647 on many systems).</para>
-			<para>Example:  Set(junky=${RAND(1,8)});
-			Sets junky to a random number between 1 and 8, inclusive.</para>
+			<example title="Set random number between 1 and 8, inclusive">
+			exten => s,1,Set(junky=${RAND(1,8)})
+			</example>
 		</description>
 	</function>
  ***/
diff --git a/funcs/func_sayfiles.c b/funcs/func_sayfiles.c
index 81f3259ad96bc5e9f022fce6955daa5ea209a199..7e0ece2c966ae85e5d8b7c2161bcb5dbb5a37241 100644
--- a/funcs/func_sayfiles.c
+++ b/funcs/func_sayfiles.c
@@ -42,6 +42,11 @@
 
 /*** DOCUMENTATION
 	<function name="SAYFILES" language="en_US">
+		<since>
+			<version>16.21.0</version>
+			<version>18.7.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Returns the ampersand-delimited file names that would be played by the Say applications (e.g. SayAlpha, SayDigits).
 		</synopsis>
@@ -206,6 +211,15 @@ AST_TEST_DEFINE(test_SAYFILES_function)
 		res = AST_TEST_FAIL;
 	}
 
+	/* + should be ignored and there should not be a leading & */
+	ast_str_set(&expr, 0, "${SAYFILES(+18005551212,digits)}");
+	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
+	if (strcmp(ast_str_buffer(result), "digits/1&digits/8&digits/0&digits/0&digits/5&digits/5&digits/5&digits/1&digits/2&digits/1&digits/2") != 0) {
+		ast_test_status_update(test, "SAYFILES(+18005551212,digits) test failed ('%s')\n",
+				ast_str_buffer(result));
+		res = AST_TEST_FAIL;
+	}
+
 	ast_str_set(&expr, 0, "${SAYFILES(35,number)}");
 	ast_str_substitute_variables(&result, 0, NULL, ast_str_buffer(expr));
 	if (strcmp(ast_str_buffer(result), "digits/30&digits/5") != 0) {
diff --git a/funcs/func_scramble.c b/funcs/func_scramble.c
index 01756d9a021b37b221439a13ba7069c23bb357cc..e055b2dc8a216dc74459481e56876d2843e4f4cd 100644
--- a/funcs/func_scramble.c
+++ b/funcs/func_scramble.c
@@ -32,6 +32,11 @@
 
 /*** DOCUMENTATION
 	<function name="SCRAMBLE" language="en_US">
+		<since>
+			<version>16.21.0</version>
+			<version>18.7.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Scrambles audio on a channel.
 		</synopsis>
@@ -120,8 +125,9 @@ static int scramble_callback(struct ast_audiohook *audiohook, struct ast_channel
 	}
 
 	if (frame->frametype == AST_FRAME_VOICE) { /* only invert voice frequencies */
+		ni = datastore->data;
 		/* Based on direction of frame, and confirm it is applicable */
-		if (!(direction == AST_AUDIOHOOK_DIRECTION_READ ? &ni->rx : &ni->tx)) {
+		if (!(direction == AST_AUDIOHOOK_DIRECTION_READ ? ni->rx : ni->tx)) {
 			return 0;
 		}
 		/* Scramble the sample now */
diff --git a/funcs/func_sha1.c b/funcs/func_sha1.c
index 6c96ecd05903bfeefd3181cd23029dcc95ff9de5..11685b2f2490c6667ef46d71793ffc41d32dc5a8 100644
--- a/funcs/func_sha1.c
+++ b/funcs/func_sha1.c
@@ -45,9 +45,11 @@
 		</syntax>
 		<description>
 			<para>Generate a SHA1 digest via the SHA1 algorythm.</para>
-			<para>Example:  Set(sha1hash=${SHA1(junky)})</para>
-			<para>Sets the asterisk variable sha1hash to the string <literal>60fa5675b9303eb62f99a9cd47f9f5837d18f9a0</literal>
-			which is known as his hash</para>
+			<example title="Set sha1hash variable to SHA1 hash of junky">
+			exten => s,1,Set(sha1hash=${SHA1(junky)})
+			</example>
+			<para>The example above sets the asterisk variable sha1hash to the string <literal>60fa5675b9303eb62f99a9cd47f9f5837d18f9a0</literal>
+			which is known as its hash</para>
 		</description>
 	</function>
  ***/
diff --git a/funcs/func_shell.c b/funcs/func_shell.c
index fe1debe8848466d15990353dedc4095110659216..c8c218718008c223e84bbfb4fe243976e9d287e8 100644
--- a/funcs/func_shell.c
+++ b/funcs/func_shell.c
@@ -91,7 +91,9 @@ static int shell_helper(struct ast_channel *chan, const char *cmd, char *data,
 		</syntax>
 		<description>
 			<para>Collects the output generated by a command executed by the system shell</para>
-			<para>Example:  <literal>Set(foo=${SHELL(echo bar)})</literal></para>
+			<example title="Shell example">
+			exten => s,1,Set(foo=${SHELL(echo bar)})
+			</example>
 			<note>
 				<para>The command supplied to this function will be executed by the
 				system's shell, typically specified in the SHELL environment variable. There
diff --git a/funcs/func_speex.c b/funcs/func_speex.c
index 1a88fae7f3f7837372a0ab6edb9990b2e5544875..7d05d6f43cfaac6a52087a61d1d358ac31e4aeaf 100644
--- a/funcs/func_speex.c
+++ b/funcs/func_speex.c
@@ -66,9 +66,10 @@
 			analog lines, but could be useful for other channels as well. The target volume
 			is set with a number between <literal>1-32768</literal>. The larger the number
 			the louder (more gain) the channel will receive.</para>
-			<para>Examples:</para>
-			<para>exten => 1,1,Set(AGC(rx)=8000)</para>
-			<para>exten => 1,2,Set(AGC(tx)=off)</para>
+			<example title="Apply automatic gain control">
+			exten => 1,1,Set(AGC(rx)=8000)
+			exten => 1,2,Set(AGC(tx)=off)
+			</example>
 		</description>
 	</function>
 	<function name="DENOISE" language="en_US">
@@ -87,9 +88,10 @@
 			that it is executed on. It is very useful for noisy analog lines, especially
 			when adjusting gains or using AGC. Use <literal>rx</literal> for audio received from the channel
 			and <literal>tx</literal> to apply the filter to the audio being sent to the channel.</para>
-			<para>Examples:</para>
-			<para>exten => 1,1,Set(DENOISE(rx)=on)</para>
-			<para>exten => 1,2,Set(DENOISE(tx)=off)</para>
+			<example title="Apply noise reduction">
+			exten => 1,1,Set(DENOISE(rx)=on)
+			exten => 1,2,Set(DENOISE(tx)=off)
+			</example>
 		</description>
 	</function>
  ***/
diff --git a/funcs/func_srv.c b/funcs/func_srv.c
index c8506b3a76c543255c8217e64dc43d4020b5fe71..4ac9e6972167bef21f93c9205f01e51db4446d5f 100644
--- a/funcs/func_srv.c
+++ b/funcs/func_srv.c
@@ -65,6 +65,16 @@
 				as <literal>getnum</literal>, then it will return the total number of results
 				that are available.</para>
 			</parameter>
+			<parameter name="field" required="false">
+				<para>The field of the result to retrieve.</para>
+				<para>The fields that can be retrieved are:</para>
+				<enumlist>
+					<enum name="host"/>
+					<enum name="port"/>
+					<enum name="priority"/>
+					<enum name="weight"/>
+				</enumlist>
+			</parameter>
 		</syntax>
 		<description>
 			<para>This function will retrieve results from a previous use
diff --git a/funcs/func_strings.c b/funcs/func_strings.c
index 28fe6e0b5befda256fffcaf6fd86881035021478..598e6a47b2586551d35557000d948006fbeb610f 100644
--- a/funcs/func_strings.c
+++ b/funcs/func_strings.c
@@ -4,7 +4,7 @@
  * Copyright (C) 2005-2006, Digium, Inc.
  * Portions Copyright (C) 2005, Tilghman Lesher.  All rights reserved.
  * Portions Copyright (C) 2005, Anthony Minessale II
- * Portions Copyright (C) 2021, Naveen Albert
+ * Portions Copyright (C) 2021, 2022, Naveen Albert
  *
  * See http://www.asterisk.org for more information about
  * the Asterisk project. Please do not directly contact
@@ -62,7 +62,10 @@ AST_THREADSTORAGE(tmp_buf);
 			carriage return, and tab characters, respectively.  Also, octal and hexadecimal specifications are recognized
 			by the patterns <literal>\0nnn</literal> and <literal>\xHH</literal>, respectively.  For example, if you wanted
 			to encode a comma as the delimiter, you could use either <literal>\054</literal> or <literal>\x2C</literal>.</para>
-			<para>Example: If ${example} contains <literal>ex-amp-le</literal>, then ${FIELDQTY(example,-)} returns 3.</para>
+			<example title="Prints 3">
+			exten => s,1,Set(example=ex-amp-le)
+				same => n,NoOp(${FIELDQTY(example,-)})
+			</example>
 		</description>
 	</function>
 	<function name="FIELDNUM" language="en_US">
@@ -83,7 +86,10 @@ AST_THREADSTORAGE(tmp_buf);
 			carriage return, and tab characters, respectively.  Also, octal and hexadecimal specifications are recognized
 			by the patterns <literal>\0nnn</literal> and <literal>\xHH</literal>, respectively.  For example, if you wanted
 			to encode a comma as the delimiter, you could use either <literal>\054</literal> or <literal>\x2C</literal>.</para>
-		        <para>Example: If ${example} contains <literal>ex-amp-le</literal>, then ${FIELDNUM(example,-,amp)} returns 2.</para>
+			<example title="Prints 2">
+			exten => s,1,Set(example=ex-amp-le)
+				same => n,NoOp(${FIELDNUM(example,-,amp)})
+			</example>
 		</description>
 	</function>
 	<function name="LISTFILTER" language="en_US">
@@ -155,6 +161,11 @@ AST_THREADSTORAGE(tmp_buf);
 		</description>
 	</function>
 	<function name="STRBETWEEN" language="en_US">
+		<since>
+			<version>16.21.0</version>
+			<version>18.7.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Inserts a substring between each character in a string.
 		</synopsis>
@@ -172,6 +183,51 @@ AST_THREADSTORAGE(tmp_buf);
 			</example>
 		</description>
 	</function>
+	<function name="TRIM" language="en_US">
+		<synopsis>
+			Trim leading and trailing whitespace in a string
+		</synopsis>
+		<syntax>
+			<parameter name="string" required="true" />
+		</syntax>
+		<description>
+			<para>Replaces all leading and trailing whitespace in the provided string.</para>
+		</description>
+		<see-also>
+			<ref type="function">LTRIM</ref>
+			<ref type="function">RTRIM</ref>
+		</see-also>
+	</function>
+	<function name="LTRIM" language="en_US">
+		<synopsis>
+			Trim leading whitespace in a string
+		</synopsis>
+		<syntax>
+			<parameter name="string" required="true" />
+		</syntax>
+		<description>
+			<para>Replaces all leading whitespace in the provided string.</para>
+		</description>
+		<see-also>
+			<ref type="function">TRIM</ref>
+			<ref type="function">RTRIM</ref>
+		</see-also>
+	</function>
+	<function name="RTRIM" language="en_US">
+		<synopsis>
+			Trim trailing whitespace in a string
+		</synopsis>
+		<syntax>
+			<parameter name="string" required="true" />
+		</syntax>
+		<description>
+			<para>Replaces all trailing whitespace in the provided string.</para>
+		</description>
+		<see-also>
+			<ref type="function">TRIM</ref>
+			<ref type="function">LTRIM</ref>
+		</see-also>
+	</function>
 	<function name="PASSTHRU" language="en_US">
 		<synopsis>
 			Pass the given argument back as a value.
@@ -185,8 +241,10 @@ AST_THREADSTORAGE(tmp_buf);
 			string, instead.</para>
 			<note><para>The functions which take a variable name need to be passed var and not
 			${var}.  Similarly, use PASSTHRU() and not ${PASSTHRU()}.</para></note>
-			<para>Example: ${CHANNEL} contains SIP/321-1</para>
-			<para>         ${CUT(PASSTHRU(${CUT(CHANNEL,-,1)}),/,2)}) will return 321</para>
+			<example title="Prints 321">
+			exten => s,1,NoOp(${CHANNEL}) ; contains SIP/321-1
+				same => n,NoOp(${CUT(PASSTHRU(${CUT(CHANNEL,-,1)}),/,2)})
+			</example>
 		</description>
 	</function>
 	<function name="REGEX" language="en_US">
@@ -252,7 +310,9 @@ AST_THREADSTORAGE(tmp_buf);
 			<parameter name="string" required="true" />
 		</syntax>
 		<description>
-			<para>Example: ${KEYPADHASH(Les)} returns "537"</para>
+			<example title="Returns 537">
+			exten => s,1,Return(${KEYPADHASH(Les)})
+			</example>
 		</description>
 	</function>
 	<function name="ARRAY" language="en_US">
@@ -268,7 +328,9 @@ AST_THREADSTORAGE(tmp_buf);
 			<para>The comma-delimited list passed as a value to which the function is set will
 			be interpreted as a set of values to which the comma-delimited list of
 			variable names in the argument should be set.</para>
-			<para>Example: Set(ARRAY(var1,var2)=1,2) will set var1 to 1 and var2 to 2</para>
+			<example title="Set var1 to 1 and var2 to 2">
+			same => n,Set(ARRAY(var1,var2)=1,2)
+			</example>
 		</description>
 	</function>
 	<function name="STRPTIME" language="en_US">
@@ -284,7 +346,9 @@ AST_THREADSTORAGE(tmp_buf);
 			<para>This is useful for converting a date into <literal>EPOCH</literal> time,
 			possibly to pass to an application like SayUnixTime or to calculate the difference
 			between the two date strings</para>
-			<para>Example: ${STRPTIME(2006-03-01 07:30:35,America/Chicago,%Y-%m-%d %H:%M:%S)} returns 1141219835</para>
+			<example title="Prints 1141219835">
+			same => n,NoOp(${STRPTIME(2006-03-01 07:30:35,America/Chicago,%Y-%m-%d %H:%M:%S)})
+			</example>
 		</description>
 	</function>
 	<function name="STRFTIME" language="en_US">
@@ -338,7 +402,9 @@ AST_THREADSTORAGE(tmp_buf);
 			<parameter name="string" required="true" />
 		</syntax>
 		<description>
-			<para>Example: ${TOUPPER(Example)} returns "EXAMPLE"</para>
+			<example title="Prints EXAMPLE">
+			exten => s,1,NoOp(${TOUPPER(Example)})
+			</example>
 		</description>
 	</function>
 	<function name="TOLOWER" language="en_US">
@@ -349,7 +415,9 @@ AST_THREADSTORAGE(tmp_buf);
 			<parameter name="string" required="true" />
 		</syntax>
 		<description>
-			<para>Example: ${TOLOWER(Example)} returns "example"</para>
+			<example title="Prints example">
+			exten => s,1,NoOp(${TOLOWER(Example)})
+			</example>
 		</description>
 	</function>
 	<function name="LEN" language="en_US">
@@ -360,7 +428,9 @@ AST_THREADSTORAGE(tmp_buf);
 			<parameter name="string" required="true" />
 		</syntax>
 		<description>
-			<para>Example: ${LEN(example)} returns 7</para>
+			<example title="Prints 7">
+			exten => s,1,NoOp(${LEN(example)})
+			</example>
 		</description>
 	</function>
 	<function name="QUOTE" language="en_US">
@@ -394,11 +464,12 @@ AST_THREADSTORAGE(tmp_buf);
 			<parameter name="delimiter" required="false" default="," />
 		</syntax>
 		<description>
-			<para>Example:</para>
-			<para>exten => s,1,Set(array=one,two,three)</para>
-			<para>exten => s,n,While($["${SET(var=${SHIFT(array)})}" != ""])</para>
-			<para>exten => s,n,NoOp(var is ${var})</para>
-			<para>exten => s,n,EndWhile</para>
+			<example title="SHIFT example">
+			exten => s,1,Set(array=one,two,three)
+			exten => s,n,While($["${SET(var=${SHIFT(array)})}" != ""])
+			exten => s,n,NoOp(var is ${var})
+			exten => s,n,EndWhile
+			</example>
 			<para>This would iterate over each value in array, left to right, and
 				would result in NoOp(var is one), NoOp(var is two), and
 				NoOp(var is three) being executed.
@@ -414,11 +485,12 @@ AST_THREADSTORAGE(tmp_buf);
 			<parameter name="delimiter" required="false" default="," />
 		</syntax>
 		<description>
-			<para>Example:</para>
-			<para>exten => s,1,Set(array=one,two,three)</para>
-			<para>exten => s,n,While($["${SET(var=${POP(array)})}" != ""])</para>
-			<para>exten => s,n,NoOp(var is ${var})</para>
-			<para>exten => s,n,EndWhile</para>
+			<example title="POP example">
+			exten => s,1,Set(array=one,two,three)
+			exten => s,n,While($["${SET(var=${POP(array)})}" != ""])
+			exten => s,n,NoOp(var is ${var})
+			exten => s,n,EndWhile
+			</example>
 			<para>This would iterate over each value in array, right to left, and
 				would result in NoOp(var is three), NoOp(var is two), and
 				NoOp(var is one) being executed.
@@ -434,7 +506,10 @@ AST_THREADSTORAGE(tmp_buf);
 			<parameter name="delimiter" required="false" default="," />
 		</syntax>
 		<description>
-			<para>Example: Set(PUSH(array)=one,two,three) would append one,
+			<example title="PUSH example">
+			exten => s,1,Set(PUSH(array)=one,two,three)
+			</example>
+			<para>This would append one,
 				two, and three to the end of the values stored in the variable
 				"array".
 			</para>
@@ -449,7 +524,10 @@ AST_THREADSTORAGE(tmp_buf);
 			<parameter name="delimiter" required="false" default="," />
 		</syntax>
 		<description>
-			<para>Example: Set(UNSHIFT(array)=one,two,three) would insert one,
+			<example title="UNSHIFT example">
+			exten => s,1,Set(UNSHIFT(array)=one,two,three)
+			</example>
+			<para>This would insert one,
 				two, and three before the values stored in the variable
 				"array".
 			</para>
@@ -1012,6 +1090,84 @@ static struct ast_custom_function strbetween_function = {
 	.read2 = strbetween,
 };
 
+#define ltrim(s) while (isspace(*s)) s++;
+#define rtrim(s) { \
+	if (s) { \
+		char *back = s + strlen(s); \
+		while (back != s && isspace(*--back)); \
+		if (*s) { \
+			*(back + 1) = '\0'; \
+		} \
+	} \
+}
+
+static int function_trim(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+	char *c;
+
+	if (ast_strlen_zero(data)) {
+		return -1;
+	}
+
+	c = ast_strdupa(data);
+	ltrim(c);
+	rtrim(c);
+
+	ast_copy_string(buf, c, len);
+
+	return 0;
+}
+
+static int function_ltrim(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+	char *c;
+
+	if (ast_strlen_zero(data)) {
+		return -1;
+	}
+
+	c = data;
+	ltrim(c);
+
+	ast_copy_string(buf, c, len);
+
+	return 0;
+}
+
+static int function_rtrim(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+	char *c;
+
+	if (ast_strlen_zero(data)) {
+		return -1;
+	}
+
+	c = ast_strdupa(data);
+	rtrim(c);
+
+	ast_copy_string(buf, c, len);
+
+	return 0;
+}
+
+#undef ltrim
+#undef rtrim
+
+static struct ast_custom_function trim_function = {
+	.name = "TRIM",
+	.read = function_trim,
+};
+
+static struct ast_custom_function ltrim_function = {
+	.name = "LTRIM",
+	.read = function_ltrim,
+};
+
+static struct ast_custom_function rtrim_function = {
+	.name = "RTRIM",
+	.read = function_rtrim,
+};
+
 static int regex(struct ast_channel *chan, const char *cmd, char *parse, char *buf,
 		 size_t len)
 {
@@ -2093,6 +2249,59 @@ AST_TEST_DEFINE(test_STRBETWEEN)
 
 	return res;
 }
+
+AST_TEST_DEFINE(test_TRIM)
+{
+	int i, res = AST_TEST_PASS;
+	struct ast_channel *chan; /* dummy channel */
+	struct ast_str *str; /* fancy string for holding comparing value */
+
+	const char *test_strings[][5] = {
+		{"TRIM", "  abcd ", "abcd"},
+		{"LTRIM", " abcd ", "abcd "},
+		{"RTRIM", " abcd ", " abcd"},
+		{"TRIM", "abcd", "abcd"},
+		{"TRIM", " a b c d ", "a b c d"},
+	};
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "func_TRIM";
+		info->category = "/funcs/func_strings/";
+		info->summary = "Test TRIM functions";
+		info->description = "Verify TRIM behavior";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	if (!(chan = ast_dummy_channel_alloc())) {
+		ast_test_status_update(test, "Unable to allocate dummy channel\n");
+		return AST_TEST_FAIL;
+	}
+
+	if (!(str = ast_str_create(64))) {
+		ast_test_status_update(test, "Unable to allocate dynamic string buffer\n");
+		ast_channel_release(chan);
+		return AST_TEST_FAIL;
+	}
+
+	for (i = 0; i < ARRAY_LEN(test_strings); i++) {
+		char tmp[512], tmp2[512] = "";
+
+		snprintf(tmp, sizeof(tmp), "${%s(%s)}", test_strings[i][0], test_strings[i][1]);
+		ast_str_substitute_variables(&str, 0, chan, tmp);
+		if (strcmp(test_strings[i][2], ast_str_buffer(str))) {
+			ast_test_status_update(test, "Format string '%s' substituted to '%s'.  Expected '%s'.\n", test_strings[i][0], tmp2, test_strings[i][2]);
+			res = AST_TEST_FAIL;
+		}
+	}
+
+	ast_free(str);
+	ast_channel_release(chan);
+
+	return res;
+}
 #endif
 
 static int unload_module(void)
@@ -2104,6 +2313,7 @@ static int unload_module(void)
 	AST_TEST_UNREGISTER(test_FILTER);
 	AST_TEST_UNREGISTER(test_STRREPLACE);
 	AST_TEST_UNREGISTER(test_STRBETWEEN);
+	AST_TEST_UNREGISTER(test_TRIM);
 	res |= ast_custom_function_unregister(&fieldqty_function);
 	res |= ast_custom_function_unregister(&fieldnum_function);
 	res |= ast_custom_function_unregister(&filter_function);
@@ -2130,6 +2340,9 @@ static int unload_module(void)
 	res |= ast_custom_function_unregister(&push_function);
 	res |= ast_custom_function_unregister(&unshift_function);
 	res |= ast_custom_function_unregister(&passthru_function);
+	res |= ast_custom_function_unregister(&trim_function);
+	res |= ast_custom_function_unregister(&ltrim_function);
+	res |= ast_custom_function_unregister(&rtrim_function);
 
 	return res;
 }
@@ -2143,6 +2356,7 @@ static int load_module(void)
 	AST_TEST_REGISTER(test_FILTER);
 	AST_TEST_REGISTER(test_STRREPLACE);
 	AST_TEST_REGISTER(test_STRBETWEEN);
+	AST_TEST_REGISTER(test_TRIM);
 	res |= ast_custom_function_register(&fieldqty_function);
 	res |= ast_custom_function_register(&fieldnum_function);
 	res |= ast_custom_function_register(&filter_function);
@@ -2169,6 +2383,9 @@ static int load_module(void)
 	res |= ast_custom_function_register(&push_function);
 	res |= ast_custom_function_register(&unshift_function);
 	res |= ast_custom_function_register(&passthru_function);
+	res |= ast_custom_function_register(&trim_function);
+	res |= ast_custom_function_register(&ltrim_function);
+	res |= ast_custom_function_register(&rtrim_function);
 
 	return res;
 }
diff --git a/funcs/func_talkdetect.c b/funcs/func_talkdetect.c
index 8453111dd203b9cf85c58fc24c246d0bf4e61a7b..257ecda8f31ac8b27c84646754e86b3bd3584385 100644
--- a/funcs/func_talkdetect.c
+++ b/funcs/func_talkdetect.c
@@ -42,6 +42,9 @@
 
 /*** DOCUMENTATION
 	<function name="TALK_DETECT" language="en_US">
+		<since>
+			<version>12.4.0</version>
+		</since>
 		<synopsis>
 			Raises notifications when Asterisk detects silence or talking on a channel.
 		</synopsis>
@@ -100,11 +103,18 @@
 			natural speech.</para>
 			<para>By default this value is 2500ms. Valid values are 1
 			through 2^31.</para>
-			<para>Example:</para>
-			<para>same => n,Set(TALK_DETECT(set)=)     ; Enable talk detection</para>
-			<para>same => n,Set(TALK_DETECT(set)=1200) ; Update existing talk detection's silence threshold to 1200 ms</para>
-			<para>same => n,Set(TALK_DETECT(remove)=)  ; Remove talk detection</para>
-			<para>same => n,Set(TALK_DETECT(set)=,128) ; Enable and set talk threshold to 128</para>
+			<example title="Enable talk detection">
+			same => n,Set(TALK_DETECT(set)=)
+			</example>
+			<example title="Update existing talk detection's silence threshold to 1200 ms">
+			same => n,Set(TALK_DETECT(set)=1200)
+			</example>
+			<example title="Remove talk detection">
+			same => n,Set(TALK_DETECT(remove)=)
+			</example>
+			<example title="Enable and set talk threshold to 128">
+			same => n,Set(TALK_DETECT(set)=,128)
+			</example>
 			<para>This function will set the following variables:</para>
 			<note>
 				<para>The TALK_DETECT function uses an audiohook to inspect the
@@ -114,10 +124,11 @@
 				it typically makes sense to place functions that modify the voice
 				media data prior to placing the TALK_DETECT function, as this will
 				yield better results.</para>
-				<para>Example:</para>
-				<para>same => n,Set(DENOISE(rx)=on)    ; Denoise received audio</para>
-				<para>same => n,Set(TALK_DETECT(set)=) ; Perform talk detection on the denoised received audio</para>
 			</note>
+			<example title="Denoise and then perform talk detection">
+			same => n,Set(DENOISE(rx)=on)    ; Denoise received audio
+			same => n,Set(TALK_DETECT(set)=) ; Perform talk detection on the denoised received audio
+			</example>
 		</description>
 	</function>
  ***/
diff --git a/funcs/func_version.c b/funcs/func_version.c
index 01cb74845484f11083e0087184ceb580a1d4bd54..baef884ac47ee3610adccca31bd4cb790ba2fa1c 100644
--- a/funcs/func_version.c
+++ b/funcs/func_version.c
@@ -47,7 +47,7 @@
 				<enumlist>
 					<enum name="ASTERISK_VERSION_NUM">
 						<para>A string of digits is returned, e.g. 10602 for 1.6.2 or 100300 for 10.3.0,
-						or 999999 when using an SVN build.</para>
+						or 999999 when using a Git build.</para>
 					</enum>
 					<enum name="BUILD_USER">
 						<para>The string representing the user's name whose account
@@ -73,9 +73,10 @@
 			</parameter>
 		</syntax>
 		<description>
-			<para>If there are no arguments, return the version of Asterisk in this format: SVN-branch-1.4-r44830M</para>
-			<para>Example:  Set(junky=${VERSION()};</para>
-			<para>Sets junky to the string <literal>SVN-branch-1.6-r74830M</literal>, or possibly, <literal>SVN-trunk-r45126M</literal>.</para>
+			<para>If there are no arguments, return the version of Asterisk in this format: 18.12.0</para>
+			<example title="Get current version">
+			same => n,Set(junky=${VERSION()} ; sets junky to 18.12.0, or possibly GITMasterxxxxxx
+			</example>
 		</description>
 	</function>
  ***/
diff --git a/funcs/func_vmcount.c b/funcs/func_vmcount.c
index 66007034a1027c2e34b743315894f147582cae6f..b6302ccbae7ca1075b6f929b7b419418d6de2194 100644
--- a/funcs/func_vmcount.c
+++ b/funcs/func_vmcount.c
@@ -57,7 +57,9 @@
 		<description>
 			<para>Count the number of voicemails in a specified mailbox, you could also specify
 			the mailbox <replaceable>folder</replaceable>.</para>
-			<para>Example: <literal>exten => s,1,Set(foo=${VMCOUNT(125@default)})</literal></para>
+			<example title="Mailbox folder count">
+			exten => s,1,Set(foo=${VMCOUNT(125@default)})
+			</example>
 			<para>An ampersand-separated list of mailboxes may be specified to count voicemails in
 			multiple mailboxes. If a folder is specified, this will apply to all mailboxes specified.</para>
                         <example title="Multiple mailbox inbox count">
diff --git a/funcs/func_volume.c b/funcs/func_volume.c
index a20bb76042ade14050e192a33045fbb85049cdc7..93728728d6a40b8b69177d99a3e33fafb954e7d9 100644
--- a/funcs/func_volume.c
+++ b/funcs/func_volume.c
@@ -59,11 +59,24 @@
 		<description>
 			<para>The VOLUME function can be used to increase or decrease the <literal>tx</literal> or
 			<literal>rx</literal> gain of any channel.</para>
-			<para>For example:</para>
-			<para>Set(VOLUME(TX)=3)</para>
-			<para>Set(VOLUME(RX)=2)</para>
-			<para>Set(VOLUME(TX,p)=3)</para>
-			<para>Set(VOLUME(RX,p)=3)</para>
+			<example title="Increase volume">
+			same => n,Set(VOLUME(TX)=3)
+			</example>
+			<example title="Increase volume">
+			same => n,Set(VOLUME(RX)=2)
+			</example>
+			<example title="Increase volume with DTMF control">
+			same => n,Set(VOLUME(TX,p)=3)
+			</example>
+			<example title="Increase RX volume with DTMF control">
+			same => n,Set(VOLUME(RX,p)=3)
+			</example>
+			<example title="Decrease RX volume">
+			same => n,Set(VOLUME(RX)=-4)
+			</example>
+			<example title="Reset to normal">
+			same => n,Set(VOLUME(RX)=0)
+			</example>
 		</description>
 	</function>
  ***/
diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h
index 4f81421f3a255055b7765f347335f7f4e716d970..65e403fb49936994faaadf88fdfdf8095fdf8667 100644
--- a/include/asterisk/_private.h
+++ b/include/asterisk/_private.h
@@ -37,6 +37,7 @@ int ast_term_init(void);		/*!< Provided by term.c */
 int astdb_init(void);			/*!< Provided by db.c */
 int ast_channels_init(void);		/*!< Provided by channel.c */
 void ast_builtins_init(void);		/*!< Provided by cli.c */
+void ast_cli_channels_init(void);	/*!< Provided by cli.c */
 int ast_cli_perms_init(int reload);	/*!< Provided by cli.c */
 void dnsmgr_start_refresh(void);	/*!< Provided by dnsmgr.c */
 int ast_dns_system_resolver_init(void); /*!< Provided by dns_system_resolver.c */
diff --git a/include/asterisk/astdb.h b/include/asterisk/astdb.h
index c511be8a762f92a02669eda1df1778eb1b3ddf35..216b8d3d9d41f2fb09d5ed332b39f4012993c85b 100644
--- a/include/asterisk/astdb.h
+++ b/include/asterisk/astdb.h
@@ -56,6 +56,17 @@ int ast_db_put(const char *family, const char *key, const char *value);
 /*! \brief Delete entry in astdb */
 int ast_db_del(const char *family, const char *key);
 
+/*! \brief Same as ast_db_del, but with more stringent error checking
+ *
+ * \details
+ * Unlike ast_db_del, if the key does not exist in the first place,
+ * an error is emitted and -1 is returned.
+ *
+ * \retval -1 An error occured (including key not found to begin with)
+ * \retval 0 Successfully deleted
+ */
+int ast_db_del2(const char *family, const char *key);
+
 /*!
  * \brief Delete one or more entries in astdb
  *
diff --git a/include/asterisk/audiohook.h b/include/asterisk/audiohook.h
index 8e93d562295c0e22ed204889e4d62ac63606d4f9..e19c8339f548610dd17e700f8ef54aa8df11e79a 100644
--- a/include/asterisk/audiohook.h
+++ b/include/asterisk/audiohook.h
@@ -118,6 +118,7 @@ struct ast_audiohook {
 	ast_audiohook_manipulate_callback manipulate_callback; /*!< Manipulation callback */
 	struct ast_audiohook_options options;                  /*!< Applicable options */
 	unsigned int hook_internal_samp_rate;                  /*!< internal read/write sample rate on the audiohook.*/
+	enum ast_audiohook_direction direction;                /*!< Intended audiohook direction, BOTH by default on init */
 	AST_LIST_ENTRY(ast_audiohook) list;                    /*!< Linked list information */
 };
 
@@ -140,6 +141,14 @@ int ast_audiohook_init(struct ast_audiohook *audiohook, enum ast_audiohook_type
  */
 int ast_audiohook_destroy(struct ast_audiohook *audiohook);
 
+/*! \brief Sets direction on audiohook
+ * \param audiohook
+ * \param direction In which direction should the audiohook feed frames, ie if we are snooping 'in', set direction to READ so that only the 'in' frames are fed to the slin factory
+ * \retval 0 on success
+ * \retval -1 on failure due to audiohook already in use or in shutdown. Can only set direction on NEW audiohooks
+ */
+int ast_audiohook_set_frame_feed_direction(struct ast_audiohook *audiohook, enum ast_audiohook_direction direction);
+
 /*! \brief Writes a frame into the audiohook structure
  * \param audiohook
  * \param direction Direction the audio frame came from
@@ -349,6 +358,16 @@ int ast_audiohook_volume_adjust(struct ast_channel *chan, enum ast_audiohook_dir
  */
 int ast_audiohook_set_mute(struct ast_channel *chan, const char *source, enum ast_audiohook_flags flag, int clear);
 
+/*! \brief Mute frames read from or written for all audiohooks on a channel
+ * \param chan Channel to muck with
+ * \param source Type of audiohooks
+ * \param flag which direction to set / clear
+ * \param clear set or clear muted frames on direction based on flag parameter
+ * \retval >=0 number of muted audiohooks
+ * \retval -1 failure
+ */
+int ast_audiohook_set_mute_all(struct ast_channel *chan, const char *source, enum ast_audiohook_flags flag, int clear);
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in
index 5dfc5403be08502fba266aea0e1caf86a6ec2012..95fb982fac2599773c8dbf0b85a4ec74f51e3238 100644
--- a/include/asterisk/autoconfig.h.in
+++ b/include/asterisk/autoconfig.h.in
@@ -408,9 +408,6 @@
 /* Define to 1 if you have the `isascii' function. */
 #undef HAVE_ISASCII
 
-/* Define to 1 if you have the ISDN4Linux library. */
-#undef HAVE_ISDNNET
-
 /* Define to 1 if you have the Jack Audio Connection Kit library. */
 #undef HAVE_JACK
 
@@ -514,15 +511,6 @@
 /* Define to 1 if you have the `memset' function. */
 #undef HAVE_MEMSET
 
-/* Define to 1 if you have the mISDN user library. */
-#undef HAVE_MISDN
-
-/* Define if your system has the MISDN_FAC_ERROR headers. */
-#undef HAVE_MISDN_FAC_ERROR
-
-/* Define if your system has the MISDN_FAC_RESULT headers. */
-#undef HAVE_MISDN_FAC_RESULT
-
 /* Define to 1 if you have the `mkdir' function. */
 #undef HAVE_MKDIR
 
@@ -547,9 +535,6 @@
 /* Define to 1 if mysql/mysql.h has my_bool defined. */
 #undef HAVE_MYSQLCLIENT_MY_BOOL
 
-/* Define to 1 if you have the Network Broadcast Sound library. */
-#undef HAVE_NBS
-
 /* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
 #undef HAVE_NDIR_H
 
@@ -604,9 +589,6 @@
 /* Define this to indicate the ${OSPTK_DESCRIP} library */
 #undef HAVE_OSPTK
 
-/* Define to 1 if you have the Open Sound System library. */
-#undef HAVE_OSS
-
 /* Define to 1 if your system defines the file flag O_EVTONLY in fcntl.h */
 #undef HAVE_O_EVTONLY
 
@@ -650,6 +632,9 @@
 /* Define to 1 if PJPROJECT has the PJSIP EVSUB Group Lock support feature. */
 #undef HAVE_PJSIP_EVSUB_GRP_LOCK
 
+/* Define to 1 if evsub requires a NOTIFY on SUBSCRIBE. */
+#undef HAVE_PJSIP_EVSUB_PENDING_NOTIFY
+
 /* Define to 1 if PJPROJECT has the PJSIP External Resolver Support feature.
    */
 #undef HAVE_PJSIP_EXTERNAL_RESOLVER
@@ -674,6 +659,10 @@
 /* Define if your system has the PJSIP_TLS_TRANSPORT_PROTO headers. */
 #undef HAVE_PJSIP_TLS_TRANSPORT_PROTO
 
+/* Define to 1 if PJPROJECT has the PJSIP TLS Transport Restart Support
+   feature. */
+#undef HAVE_PJSIP_TLS_TRANSPORT_RESTART
+
 /* Define if your system has the PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE
    headers. */
 #undef HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE
@@ -927,9 +916,6 @@
 /* Define to 1 if you have the speex_preprocess_ctl library. */
 #undef HAVE_SPEEX_PREPROCESS
 
-/* Define to 1 if you have the SQLite library. */
-#undef HAVE_SQLITE
-
 /* Define to 1 if you have the SQLite library. */
 #undef HAVE_SQLITE3
 
@@ -1080,9 +1066,6 @@
 /* Define to 1 if `_u._ext.nsaddrs' is a member of `struct __res_state'. */
 #undef HAVE_STRUCT___RES_STATE__U__EXT_NSADDRS
 
-/* Define to 1 if you have the mISDN Supplemental Services library. */
-#undef HAVE_SUPPSERV
-
 /* Define to 1 if you have the `swapctl' function. */
 #undef HAVE_SWAPCTL
 
@@ -1267,9 +1250,6 @@
 /* Define if your system has OV_CALLBACKS_NOCLOSE declared. */
 #undef HAVE_VORBIS_OPEN_CALLBACKS
 
-/* Define if your system has the VoiceTronix API libraries. */
-#undef HAVE_VPB
-
 /* Define to 1 if you have the `vprintf' function. */
 #undef HAVE_VPRINTF
 
@@ -1367,9 +1347,6 @@
    slash. */
 #undef LSTAT_FOLLOWS_SLASHED_SYMLINK
 
-/* Build chan_misdn for mISDN 1.2 or later. */
-#undef MISDN_1_2
-
 /* Define to the address where bug reports for this package should be sent. */
 #undef PACKAGE_BUGREPORT
 
diff --git a/include/asterisk/callerid.h b/include/asterisk/callerid.h
index 460f105c18fb010cc56c7d99db7aff8980783a6e..16a3df11f49ce00cba3efb44c400435526f65185 100644
--- a/include/asterisk/callerid.h
+++ b/include/asterisk/callerid.h
@@ -55,6 +55,7 @@
 #define CID_UNKNOWN_NUMBER		(1 << 3)
 #define CID_MSGWAITING			(1 << 4)
 #define CID_NOMSGWAITING		(1 << 5)
+#define CID_QUALIFIER			(1 << 6)
 
 #define CID_SIG_BELL	1
 #define CID_SIG_V23	2
@@ -67,6 +68,12 @@
 #define CID_START_POLARITY_IN 	3
 #define CID_START_DTMF_NOALERT	4
 
+/* Caller ID message formats */
+/*! SDMF - number only */
+#define CID_TYPE_SDMF			0x00
+/*! MDMF - name, number, etc. */
+#define CID_TYPE_MDMF			0x01
+
 /* defines dealing with message waiting indication generation */
 /*! MWI SDMF format */
 #define CID_MWI_TYPE_SDMF		0x00
@@ -101,7 +108,26 @@ void callerid_init(void);
  * \return It returns the size
  * (in bytes) of the data (if it returns a size of 0, there is probably an error)
  */
-int callerid_generate(unsigned char *buf, const char *number, const char *name, int flags, int callwaiting, struct ast_format *codec);
+int callerid_generate(unsigned char *buf, const char *number, const char *name,	int flags, int callwaiting, struct ast_format *codec);
+
+/*! \brief Generates a CallerID FSK stream in ulaw format suitable for transmission.
+ * \param buf Buffer to use. If "buf" is supplied, it will use that buffer instead of allocating its own.
+ *   "buf" must be at least 32000 bytes in size of you want to be sure you don't have an overrun.
+ * \param number Use NULL for no number or "P" for "private"
+ * \param name name to be used
+ * \param ddn Dialable Directory Number (or NULL)
+ * \param redirecting Redirecting reason
+ * \param flags passed flags
+ * \param format Message format
+ * \param callwaiting callwaiting flag
+ * \param codec -- either AST_FORMAT_ULAW or AST_FORMAT_ALAW
+ * \details
+ * This function creates a stream of callerid (a callerid spill) data in ulaw format.
+ * \return It returns the size
+ * (in bytes) of the data (if it returns a size of 0, there is probably an error)
+ */
+int callerid_full_generate(unsigned char *buf, const char *number, const char *name, const char *ddn, int redirecting,
+	int flags, int format, int callwaiting, struct ast_format *codec);
 
 /*! \brief Create a callerID state machine
  * \param cid_signalling Type of signalling in use
@@ -177,6 +203,23 @@ void callerid_free(struct callerid_state *cid);
  */
 int ast_callerid_generate(unsigned char *buf, const char *name, const char *number, struct ast_format *codec);
 
+/*! \brief Generate Caller-ID spill from the "callerid" field of asterisk (in e-mail address like format)
+ * \param buf buffer for output samples. See callerid_generate() for details regarding buffer.
+ * \param name Caller-ID Name
+ * \param number Caller-ID Number
+ * \param ddn Dialable Directory Number (or NULL)
+ * \param redirecting Redirecting Reason (-1 if N/A)
+ * \param pres Presentation (0 for default)
+ * \param qualifier Call Qualifier (0 for no, 1 for yes)
+ * \param format Message Format
+ * \param codec Asterisk codec (either AST_FORMAT_ALAW or AST_FORMAT_ULAW)
+ *
+ * \details
+ * Like ast_callerid_generate but with additional parameters.
+ */
+int ast_callerid_full_generate(unsigned char *buf, const char *name, const char *number,
+	const char *ddn, int redirecting, int pres, int qualifier, int format, struct ast_format *codec);
+
 /*!
  * \brief Generate message waiting indicator
  * \param buf
@@ -198,6 +241,12 @@ int ast_callerid_vmwi_generate(unsigned char *buf, int active, int type, struct
  */
 int ast_callerid_callwaiting_generate(unsigned char *buf, const char *name, const char *number, struct ast_format *codec);
 
+/*! \brief Generate Caller-ID spill but in a format suitable for Call Waiting(tm)'s Caller*ID(tm)
+ * \see ast_callerid_generate() for other details
+ */
+int ast_callerid_callwaiting_full_generate(unsigned char *buf, const char *name, const char *number,
+	const char *ddn, int redirecting, int pres, int qualifier, struct ast_format *codec);
+
 /*! \brief Destructively parse inbuf into name and location (or number)
  * \details
  * Parses callerid stream from inbuf and changes into useable form, outputted in name and location.
diff --git a/include/asterisk/cdr.h b/include/asterisk/cdr.h
index 5935c3fc45243955502dd4c4a324cdfeee9df353..6099fa8ac2152d691f077fb753b9049ae16df379 100644
--- a/include/asterisk/cdr.h
+++ b/include/asterisk/cdr.h
@@ -225,6 +225,8 @@ enum ast_cdr_settings {
 	CDR_INITIATED_SECONDS = 1 << 5,     /*!< Include microseconds into the billing time */
 	CDR_DEBUG = 1 << 6,                 /*!< Enables extra debug statements */
 	CDR_CHANNEL_DEFAULT_ENABLED = 1 << 7, /*!< Whether CDR is enabled for each channel by default */
+	CDR_IGNORE_STATE_CHANGES = 1 << 8,	/*!< Whether to ignore bridge and other call state change events */
+	CDR_IGNORE_DIAL_CHANGES = 1 << 9,	/*!< Whether to ignore dial state changes */
 };
 
 /*! \brief CDR Batch Mode settings */
diff --git a/include/asterisk/cel.h b/include/asterisk/cel.h
index c0b8848dee98958cd6d0cad960a2ab956c0ab838..5645e8db2a373049427ed86e461d6c1027499292 100644
--- a/include/asterisk/cel.h
+++ b/include/asterisk/cel.h
@@ -73,8 +73,10 @@ enum ast_cel_event_type {
 	AST_CEL_PICKUP = 15,
 	/*! \brief this call was forwarded somewhere else  */
 	AST_CEL_FORWARD = 16,
-	/*! \brief A local channel optimization occurred */
+	/*! \brief A local channel optimization occurred, this marks the end */
 	AST_CEL_LOCAL_OPTIMIZE = 17,
+	/*! \brief A local channel optimization has begun */
+	AST_CEL_LOCAL_OPTIMIZE_BEGIN = 18,
 };
 
 /*!
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 8251e28843f427d474678360b80b3a2327cd6f5e..93ef364136a09f1f5c2e455708e1c565756e110c 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -2296,8 +2296,8 @@ int ast_senddigit_mf_end(struct ast_channel *chan);
  * \param chan channel to act upon
  * \param digit the MF digit to send, encoded in ASCII
  * \param duration the duration of a numeric digit ending in ms
- * \param duration the duration of a KP digit ending in ms
- * \param duration the duration of a ST, STP, ST2P, or ST3P digit ending in ms
+ * \param durationkp the duration of a KP digit ending in ms
+ * \param durationst the duration of a ST, STP, ST2P, or ST3P digit ending in ms
  * \param is_external 1 if called by a thread that is not the channel's media
  *                handler thread, 0 if called by the channel's media handler
  *                thread.
@@ -4153,8 +4153,6 @@ struct ast_channel_monitor {
 };
 
 /* ACCESSOR FUNCTIONS */
-/*! \brief Set the channel name */
-void ast_channel_name_set(struct ast_channel *chan, const char *name);
 
 #define DECLARE_STRINGFIELD_SETTERS_FOR(field)	\
 	void ast_channel_##field##_set(struct ast_channel *chan, const char *field); \
diff --git a/include/asterisk/config.h b/include/asterisk/config.h
index afac2290e2a79d7d2bfac64777f8cfb3df6b85cd..44ba4e487780a1652100bb57e9f38f75e9f6a0ac 100644
--- a/include/asterisk/config.h
+++ b/include/asterisk/config.h
@@ -970,10 +970,10 @@ int ast_variable_list_replace(struct ast_variable **head, struct ast_variable *r
 /*!
  * \brief Replace a variable in the given list with a new variable
  *
- * \param head A pointer to the current variable list head.  Since the variable to be
- *             replaced, this pointer may be updated with the new head.
- * \param old  A pointer to the existing variable to be replaced.
- * \param new  A pointer to the new variable that will replace the old one.
+ * \param head   A pointer to the current variable list head.  Since the variable to be
+ *               replaced, this pointer may be updated with the new head.
+ * \param oldvar A pointer to the existing variable to be replaced.
+ * \param newvar A pointer to the new variable that will replace the old one.
  *
  * \retval 0 if a variable was replaced in the list
  * \retval -1 if no replacement occured
@@ -981,8 +981,9 @@ int ast_variable_list_replace(struct ast_variable **head, struct ast_variable *r
  * \note The search for the old variable is done simply on the pointer.
  * \note If a variable is replaced, its memory is freed.
  */
-int ast_variable_list_replace_variable(struct ast_variable **head, struct ast_variable *old,
-	struct ast_variable *new);
+int ast_variable_list_replace_variable(struct ast_variable **head,
+	struct ast_variable *oldvar,
+	struct ast_variable *newvar);
 
 /*!
  * \brief Join an ast_variable list with specified separators and quoted values
@@ -1022,6 +1023,26 @@ struct ast_str *ast_variable_list_join(const struct ast_variable *head, const ch
 struct ast_variable *ast_variable_list_from_string(const char *input, const char *item_separator,
 	const char *name_value_separator);
 
+/*!
+ * \brief Parse a string into an ast_variable list.  The reverse of ast_variable_list_join
+ *
+ * \param input                The name-value pair string to parse.
+ * \param item_separator       The string used to separate the list items.
+ *                             Only the first character in the string will be used.
+ *                             If NULL, "," will be used.
+ * \param name_value_separator The string used to separate each item's name and value.
+ *                             Only the first character in the string will be used.
+ *                             If NULL, "=" will be used.
+ * \param quote_str            The string used to quote values.
+ *                             Only the first character in the string will be used.
+ *                             If NULL, '"' will be used.
+ *
+ * \retval A pointer to a list of ast_variables.
+ * \retval NULL if there was an error or no variables could be parsed.
+ */
+struct ast_variable *ast_variable_list_from_quoted_string(const char *input, const char *item_separator,
+	const char *name_value_separator, const char *quote_str);
+
 /*!
  * \brief Update variable value within a config
  *
diff --git a/include/asterisk/crypto.h b/include/asterisk/crypto.h
index 38c413f4db028710549aa2a1ce40be98e47052af..078711f5107f72a15d3171037aa92e9705a81f3d 100644
--- a/include/asterisk/crypto.h
+++ b/include/asterisk/crypto.h
@@ -30,20 +30,22 @@ extern "C" {
 #include "asterisk/optional_api.h"
 #include "asterisk/logger.h"
 
-#ifdef HAVE_CRYPTO
-#include "openssl/aes.h"
-typedef AES_KEY ast_aes_encrypt_key;
-typedef AES_KEY ast_aes_decrypt_key;
-#else /* !HAVE_CRYPTO */
-typedef char ast_aes_encrypt_key;
-typedef char ast_aes_decrypt_key;
-#endif /* HAVE_CRYPTO */
+/* We previously used the key length explicitly; replace with constant.
+ * For now, Asterisk is limited to 1024 bit (128 byte) RSA keys.
+ */
+#define AST_CRYPTO_RSA_KEY_BITS		1024
+#define AST_CRYPTO_AES_BLOCKSIZE	128
+
+struct aes_key {
+	unsigned char raw[AST_CRYPTO_AES_BLOCKSIZE / 8];
+};
+
+typedef struct aes_key ast_aes_encrypt_key;
+typedef struct aes_key ast_aes_decrypt_key;
 
 #define AST_KEY_PUBLIC	(1 << 0)
 #define AST_KEY_PRIVATE	(1 << 1)
 
-struct ast_key;
-
 /*!
  * \brief Retrieve a key
  * \param kname Name of the key we are retrieving
@@ -108,10 +110,10 @@ AST_OPTIONAL_API(int, ast_sign_bin, (struct ast_key *key, const char *msg, int m
 
 /*!
  * \brief Encrypt a message using a given private key
- * \param key a private key to use to encrypt
+ * \param dst a pointer to a buffer of at least srclen * 1.5 bytes in which the encrypted
  * \param src the message to encrypt
  * \param srclen the length of the message to encrypt
- * \param dst a pointer to a buffer of at least srclen * 1.5 bytes in which the encrypted
+ * \param key a private key to use to encrypt
  * answer will be stored
  *
  * \retval length of encrypted data on success.
@@ -122,10 +124,10 @@ AST_OPTIONAL_API(int, ast_encrypt_bin, (unsigned char *dst, const unsigned char
 
 /*!
  * \brief Decrypt a message using a given private key
- * \param key a private key to use to decrypt
+ * \param dst a pointer to a buffer of at least srclen bytes in which the decrypted
  * \param src the message to decrypt
  * \param srclen the length of the message to decrypt
- * \param dst a pointer to a buffer of at least srclen bytes in which the decrypted
+ * \param key a private key to use to decrypt
  * answer will be stored
  *
  * \retval length of decrypted data on success.
@@ -162,24 +164,30 @@ AST_OPTIONAL_API(int, ast_aes_set_decrypt_key,
  * \brief AES encrypt data
  * \param in data to be encrypted
  * \param out pointer to a buffer to hold the encrypted output
- * \param ctx address of an aes encryption context filled in with ast_aes_set_encrypt_key
+ * \param key pointer to the ast_aes_encrypt_key to use for encryption
+ * \retval <= 0 failure
+ * \retval otherwise number of bytes in output buffer
  */
-AST_OPTIONAL_API(void, ast_aes_encrypt,
-	(const unsigned char *in, unsigned char *out, const ast_aes_encrypt_key *ctx),
-	{ ast_log(LOG_WARNING, "AES encryption disabled. Install OpenSSL.\n");return; });
+AST_OPTIONAL_API(int, ast_aes_encrypt,
+	(const unsigned char *in, unsigned char *out, const ast_aes_encrypt_key *key),
+	{ ast_log(LOG_WARNING, "AES encryption disabled. Install OpenSSL.\n");return -1; });
 
 /*!
  * \brief AES decrypt data
  * \param in encrypted data
  * \param out pointer to a buffer to hold the decrypted output
- * \param ctx address of an aes encryption context filled in with ast_aes_set_decrypt_key
+ * \param key pointer to the ast_aes_decrypt_key to use for decryption
+ * \retval <= 0 failure
+ * \retval otherwise number of bytes in output buffer
  */
-AST_OPTIONAL_API(void, ast_aes_decrypt,
-	(const unsigned char *in, unsigned char *out, const ast_aes_decrypt_key *ctx),
-	{ ast_log(LOG_WARNING, "AES encryption disabled. Install OpenSSL.\n");return; });
+AST_OPTIONAL_API(int, ast_aes_decrypt,
+	(const unsigned char *in, unsigned char *out, const ast_aes_decrypt_key *key),
+	{ ast_log(LOG_WARNING, "AES encryption disabled. Install OpenSSL.\n");return -1; });
 
 AST_OPTIONAL_API(int, ast_crypto_loaded, (void), { return 0; });
 
+AST_OPTIONAL_API(int, ast_crypto_reload, (void), { return 0; });
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
diff --git a/include/asterisk/doxygen/licensing.h b/include/asterisk/doxygen/licensing.h
index 10e5e40205ab1cc0117b96281a451ddebe756889..fa9e73e27f1d03561c07a70b2f9be1e0eb83d7e2 100644
--- a/include/asterisk/doxygen/licensing.h
+++ b/include/asterisk/doxygen/licensing.h
@@ -134,7 +134,7 @@
  * \li libsqlite3.so.0
  * \li libss7.so.1
  * \li libssl.so.0.9.8 (chan_h323)
- * \li libstdc++.so (chan_h323, chan_vpb)
+ * \li libstdc++.so (chan_h323)
  * \li libsuppserv.so
  * \li libsybdb.so.5
  * \li libsysfs.so.2
@@ -144,7 +144,6 @@
  * \li libtonezone.so.1.0
  * \li libvorbis.so.0
  * \li libvorbisenc.so.2
- * \li libvpb.a (chan_vpb)
  * \li libwrap.so.0
  * \li libxcb-xlib.so.0
  * \li libxcb.so.1
diff --git a/include/asterisk/doxyref.h b/include/asterisk/doxyref.h
index d6daa85c80e1de12d0f6b0f25dcdf66592fd605b..0f6e36126b94718d04578facfcc92121d7ff7ef9 100644
--- a/include/asterisk/doxyref.h
+++ b/include/asterisk/doxyref.h
@@ -54,7 +54,7 @@
  *
  * \section weblinks Web sites
  * \arg \b Main:  Asterisk Developer's website https://www.asterisk.org/developers/
- * \arg \b Bugs: The Issue Tracker https://issues.asterisk.org
+ * \arg \b Bugs: The Issue Tracker https://github.com/asterisk/asterisk/issues/
  * \arg \b Lists: List Server http://lists.digium.com
  * \arg \b Wiki: The Asterisk Wiki 	https://wiki.asterisk..org
  * \arg \b Docs: The Asterisk Documentation Project http://www.asteriskdocs.org
diff --git a/include/asterisk/dundi.h b/include/asterisk/dundi.h
index 3f73c1937422b22a58dce7210ff6f4b4b973bfff..ed120018cfd05f6ec01231ebbd34320a4b1c35ec 100644
--- a/include/asterisk/dundi.h
+++ b/include/asterisk/dundi.h
@@ -59,6 +59,8 @@ enum {
 	DUNDI_PROTO_SIP  = 2,
 	/*! ITU H.323 */
 	DUNDI_PROTO_H323 = 3,
+	/*! PJSIP */
+	DUNDI_PROTO_PJSIP = 4,
 };
 
 enum {
diff --git a/include/asterisk/features_config.h b/include/asterisk/features_config.h
index 52533693b59688629beaf88711bda62def020230..9d626ee0b7c4eb9980c73de2a137a97f7b9a954c 100644
--- a/include/asterisk/features_config.h
+++ b/include/asterisk/features_config.h
@@ -72,6 +72,8 @@ struct ast_features_xfer_config {
 		AST_STRING_FIELD(transferretrysound);
 		/*! Sound played when an invalid extension is dialed, and the transferer is being returned to the call. */
 		AST_STRING_FIELD(transferinvalidsound);
+		/*! Sound to play to announce the transfer process has started. */
+		AST_STRING_FIELD_EXTENDED(transferannouncesound);
 	);
 	/*! Seconds allowed between digit presses when dialing transfer destination */
 	unsigned int transferdigittimeout;
diff --git a/include/asterisk/file.h b/include/asterisk/file.h
index 4e7c505d853ca8aaa8a87a2a5ac8f11d700ae23c..4fade8eb339258e991632a82a3659486a0d889c2 100644
--- a/include/asterisk/file.h
+++ b/include/asterisk/file.h
@@ -80,6 +80,7 @@ int ast_streamfile(struct ast_channel *c, const char *filename, const char *pref
  * \brief stream file until digit
  * If the file name is non-empty, try to play it.
  * \note If digits == "" then we can simply check for non-zero.
+ * \note If a failure is encountered, the stream will be closed before returning.
  * \retval 0 if success.
  * \retval -1 if error.
  * \retval digit if interrupted by a digit.
diff --git a/include/asterisk/format_cache.h b/include/asterisk/format_cache.h
index ca2eed39bf5ca8b0098b0646b2c976bc2adab5e2..20b9d3afbbc2bf2ede02ac13b7fcaaae1dd87df4 100644
--- a/include/asterisk/format_cache.h
+++ b/include/asterisk/format_cache.h
@@ -83,11 +83,6 @@ extern struct ast_format *ast_format_ulaw;
  */
 extern struct ast_format *ast_format_alaw;
 
-/*!
- * \brief Built-in cached testlaw format.
- */
-extern struct ast_format *ast_format_testlaw;
-
 /*!
  * \brief Built-in cached gsm format.
  */
diff --git a/include/asterisk/format_compatibility.h b/include/asterisk/format_compatibility.h
index 0420ec69d9773428397108910f1212fa9270027f..e6a99b3cc8ad42fa37bc39f747490687a59cbca6 100644
--- a/include/asterisk/format_compatibility.h
+++ b/include/asterisk/format_compatibility.h
@@ -72,7 +72,7 @@ struct ast_codec;
 /*! Opus audio (8kHz, 16kHz, 24kHz, 48Khz) */
 #define AST_FORMAT_OPUS (1ULL << 34)
 /*! Raw testing-law data (G.711) */
-#define AST_FORMAT_TESTLAW (1ULL << 47)
+/* #define AST_FORMAT_TESTLAW (1ULL << 47) */
 /*! H.261 Video */
 #define AST_FORMAT_H261 (1ULL << 18)
 /*! H.263 Video */
diff --git a/include/asterisk/json.h b/include/asterisk/json.h
index 9c42c3f13dffbe2e687ab53fa2006103d91a12e7..5b2d61422d40d4de9698d358816d1e1bce830062 100644
--- a/include/asterisk/json.h
+++ b/include/asterisk/json.h
@@ -592,6 +592,15 @@ struct ast_json *ast_json_object_get(struct ast_json *object, const char *key);
  */
 #define ast_json_object_integer_get(object, key) ast_json_integer_get(ast_json_object_get(object, key))
 
+/*!
+ * \brief Get a double field from a JSON object.
+ * \param object JSON object.
+ * \param key Key of double field to look up.
+ * \return Value of a JSON double.
+ * \retval 0 if \a real is not a JSON real number.
+ */
+#define ast_json_object_real_get(object, key) ast_json_real_get(ast_json_object_get(object, key))
+
 /*!
  * \brief Set a field in a JSON object.
  * \since 12.0.0
@@ -768,6 +777,8 @@ enum ast_json_encoding_format
 	AST_JSON_COMPACT,
 	/*! Formatted for human readability */
 	AST_JSON_PRETTY,
+	/*! Keys sorted alphabetically */
+	AST_JSON_SORTED,
 };
 
 /*!
@@ -795,6 +806,17 @@ enum ast_json_encoding_format
  */
 char *ast_json_dump_string_format(struct ast_json *root, enum ast_json_encoding_format format);
 
+/*!
+ * \brief Encode a JSON value to a string, with its keys sorted.
+ *
+ * Returned string must be freed by calling ast_json_free().
+ *
+ * \param root JSON value.
+ * \return String encoding of \a root.
+ * \retval NULL on error.
+ */
+#define ast_json_dump_string_sorted(root) ast_json_dump_string_format(root, AST_JSON_SORTED)
+
 #define ast_json_dump_str(root, dst) ast_json_dump_str_format(root, dst, AST_JSON_COMPACT)
 
 /*!
diff --git a/include/asterisk/logger.h b/include/asterisk/logger.h
index b67189030634be844cf3cecf5ece75bef78e4c44..e2a21c7f14a47a8796c9aa145189ab3356b10233 100644
--- a/include/asterisk/logger.h
+++ b/include/asterisk/logger.h
@@ -938,8 +938,6 @@ unsigned long _ast_trace_dec_indent(void);
 	ast_trace(__level < 0 ? __scope_level : __level, " " __VA_ARGS__); \
 })
 
-#define CHECK_POINT() ast_log(AST_LOG_ERROR, "Check point at %s@%s:%d\n", __func__, __FILE__, __LINE__)
-
 
 #if defined(__cplusplus) || defined(c_plusplus)
 }
diff --git a/include/asterisk/manager.h b/include/asterisk/manager.h
index d2122efbf62a6b576378cdab504f0d1526b5efb1..5e791bf9ebf8cf795ff708fb049091f47e4bb421 100644
--- a/include/asterisk/manager.h
+++ b/include/asterisk/manager.h
@@ -54,7 +54,7 @@
 - \ref manager.c Main manager code file
  */
 
-#define AMI_VERSION                     "7.0.2"
+#define AMI_VERSION                     "9.0.0"
 #define DEFAULT_MANAGER_PORT 5038	/* Default port for Asterisk management via TCP */
 #define DEFAULT_MANAGER_TLS_PORT 5039	/* Default port for Asterisk management via TCP */
 
@@ -350,6 +350,18 @@ void astman_send_list_complete_start(struct mansession *s, const struct message
  */
 void astman_send_list_complete_end(struct mansession *s);
 
+/*!
+ * \brief Enable/disable the inclusion of 'dangerous' configurations outside
+ * of the ast_config_AST_CONFIG_DIR
+ *
+ * This function can globally enable/disable the loading of configuration files
+ * outside of ast_config_AST_CONFIG_DIR.
+ *
+ * \param new_live_dangerously If true, enable the access of files outside
+ * ast_config_AST_CONFIG_DIR from astman.
+ */
+void astman_live_dangerously(int new_live_dangerously);
+
 void __attribute__((format(printf, 2, 3))) astman_append(struct mansession *s, const char *fmt, ...);
 
 /*! \brief Determine if a manager session ident is authenticated */
diff --git a/include/asterisk/module.h b/include/asterisk/module.h
index cce8735fc1b7910b0894761410e9187efb4f0056..f79dc8eb108464372c1ca516798b4b79d755fc07 100644
--- a/include/asterisk/module.h
+++ b/include/asterisk/module.h
@@ -283,7 +283,7 @@ int ast_loader_unregister(int (*updater)(void));
  * \param type The type of action that will be performed by CLI.
  *
  * \retval A possible completion of the partial match.
- * \retval NULL if no matches were found.
+ * \retval NULL if no matches were found or Asterisk is not yet fully booted.
  */
 char *ast_module_helper(const char *line, const char *word, int pos, int state, int rpos, enum ast_module_helper_type type);
 
diff --git a/include/asterisk/pbx.h b/include/asterisk/pbx.h
index f5f0691869ad7a39dbb2940bdd8b5446d38f9b66..7ef0dc794876f64340a7802d1d1b7dd46b96e17c 100644
--- a/include/asterisk/pbx.h
+++ b/include/asterisk/pbx.h
@@ -268,6 +268,23 @@ struct ast_app *pbx_findapp(const char *app);
  */
 int pbx_exec(struct ast_channel *c, struct ast_app *app, const char *data);
 
+/*!
+ * \brief Execute an application
+ *
+ * \param chan channel to execute on
+ * \param app_name name of app to execute
+ * \param app_args the data passed into the app
+ *
+ * This application executes an application by name on a given channel.
+ * It is a wrapper around pbx_exec that will perform variable substitution
+ * and then execute the application if it exists.
+ * If the application is not found, a warning is logged.
+ *
+ * \retval 0 success
+ * \retval -1 failure (including application not found)
+ */
+int ast_pbx_exec_application(struct ast_channel *chan, const char *app_name, const char *app_args);
+
 /*!
  * \brief Register a new context or find an existing one
  *
@@ -1415,7 +1432,7 @@ void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead
 /*!
  * \brief Substitutes variables, similar to pbx_substitute_variables_helper_full, but allows passing the context, extension, and priority in.
  */
-void pbx_substitute_variables_helper_full_location(struct ast_channel *c, struct varshead *headp, const char *cp1, char *cp2, int cp2_size, size_t *used, char *context, char *exten, int pri);
+void pbx_substitute_variables_helper_full_location(struct ast_channel *c, struct varshead *headp, const char *cp1, char *cp2, int cp2_size, size_t *used, const char *context, const char *exten, int pri);
 /*! @} */
 
 /*! @name Substitution routines, using dynamic string buffers
@@ -1455,6 +1472,28 @@ void ast_str_substitute_variables_varshead(struct ast_str **buf, ssize_t maxlen,
  * \param used Number of bytes read from the template.  (May be NULL)
  */
 void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *templ, size_t *used);
+
+/*!
+ * \brief Perform variable/function/expression substitution on an ast_str
+ *
+ * \param buf      Result will be placed in this buffer.
+ * \param maxlen   -1 if the buffer should not grow, 0 if the buffer
+ *                 may grow to any size, and >0 if the buffer should
+ *                 grow only to that number of bytes.
+ * \param c        A channel from which to extract values, and to pass
+ *                 to any dialplan functions.
+ * \param headp    A channel variables list to also search for variables.
+ * \param templ    Variable template to expand.
+ * \param used     Number of bytes read from the template.  (May be NULL)
+ * \param use_both Normally, if a channel is specified, headp is ignored.
+ *                 If this parameter is set to 1 and both a channel and headp
+ *                 are specified, the channel will be searched for variables
+ *                 first and any not found will be searched for in headp.
+ */
+void ast_str_substitute_variables_full2(struct ast_str **buf, ssize_t maxlen,
+	struct ast_channel *c, struct varshead *headp, const char *templ,
+	size_t *used, int use_both);
+
 /*! @} */
 
 int ast_extension_patmatch(const char *pattern, const char *data);
@@ -1501,6 +1540,25 @@ int ast_explicit_goto(struct ast_channel *chan, const char *context, const char
  */
 int ast_async_goto_if_exists(struct ast_channel *chan, const char *context, const char *exten, int priority);
 
+/*!
+ * \brief Parses a dialplan location into context, extension, priority
+ *
+ * \param chan Channel to execute on
+ * \param context Pointer to initial value for context.
+ * \param exten Pointer to initial value for exten.
+ * \param pri Pointer to initial value for pri
+ * \param ipri Pointer to integer value of priority
+ * \param mode Pointer to mode (or NULL if mode is not used)
+ * \param rest Pointer to buffer to capture rest of parsing (or NULL if not used)
+ *
+ * strsep should be used to initially populate context, exten, and pri prior
+ * to calling this function. All arguments are modified in place.
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int pbx_parse_location(struct ast_channel *chan, char **context, char **exten, char **pri, int *ipri, int *mode, char *rest);
+
 struct ast_custom_function* ast_custom_function_find(const char *name);
 
 /*!
diff --git a/include/asterisk/res_aeap.h b/include/asterisk/res_aeap.h
new file mode 100644
index 0000000000000000000000000000000000000000..b4b68cf87b16ac76aedbdc7fcafc52344c3fe906
--- /dev/null
+++ b/include/asterisk/res_aeap.h
@@ -0,0 +1,370 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ * \brief Asterisk External Application Protocol API
+ */
+
+#ifndef AST_RES_AEAP_H
+#define AST_RES_AEAP_H
+
+#include <stdint.h>
+
+struct ao2_container;
+struct ast_sorcery;
+struct ast_variable;
+
+struct ast_aeap_client_config;
+struct ast_aeap_message;
+
+#define AEAP_CONFIG_CLIENT "client"
+
+/*!
+ * \brief Retrieve the AEAP sorcery object
+ *
+ * \returns the AEAP sorcery object
+ */
+struct ast_sorcery *ast_aeap_sorcery(void);
+
+/*!
+ * \brief Retrieve a listing of all client configuration objects by protocol.
+ *
+ * \note Caller is responsible for the returned container's reference.
+ *
+ * \param protocol An optional protocol to filter on (if NULL returns all client configs)
+ *
+ * \returns A container of client configuration objects
+ */
+struct ao2_container *ast_aeap_client_configs_get(const char *protocol);
+
+/*!
+ * \brief Retrieve codec capabilities from the configuration
+ *
+ * \param cfg A configuration object
+ *
+ * \returns The configuration's codec capabilities
+ */
+const struct ast_format_cap *ast_aeap_client_config_codecs(const struct ast_aeap_client_config *cfg);
+
+/*!
+ * \brief Check a given protocol against that in an Asterisk external application configuration
+ *
+ * \param cfg A configuration object
+ * \param protocol The protocol to check
+ *
+ * \returns True if the configuration's protocol matches, false otherwise
+ */
+int ast_aeap_client_config_has_protocol(const struct ast_aeap_client_config *cfg,
+	const char *protocol);
+
+/*!
+ * \brief Retrieve a list of custom configuration fields
+ *
+ * \param id configuration id/sorcery lookup key
+ *
+ * \returns variables, or NULL on error
+ */
+struct ast_variable *ast_aeap_custom_fields_get(const char *id);
+
+/*!
+ * \brief An Asterisk external application object
+ *
+ * Connects to an external application, sending and receiving data, and
+ * dispatches received data to registered handlers.
+ */
+struct ast_aeap;
+
+/*!
+ * \brief Event raised when a message is received
+ *
+ * \param aeap An Asterisk external application object
+ * \param message The received message
+ * \param obj Associated user object
+ *
+ * \returns 0 on if message handled, otherwise non-zero
+ */
+typedef int (*ast_aeap_on_message)(struct ast_aeap *aeap, struct ast_aeap_message *message, void *obj);
+
+/*!
+ * \brief An Asterisk external application message handler
+ *
+ * Used to register message handlers with an AEAP object.
+ */
+struct ast_aeap_message_handler {
+	/*! The handler name */
+	const char *name;
+	/*! Callback triggered when on a name match */
+	ast_aeap_on_message on_message;
+};
+
+/*!
+ * \brief Event raised when a sent message does not receive a reply within
+ *        a specified time interval
+ *
+ * \param aeap An Asterisk external application object
+ * \param message The message sent that received no response
+ * \param obj Associated user object
+ */
+typedef void (*ast_aeap_on_timeout)(struct ast_aeap *aeap, struct ast_aeap_message *message, void *obj);
+
+/*!
+ * \brief Callback to cleanup a user object
+ *
+ * \param obj The user object
+ */
+typedef void (*ast_aeap_user_obj_cleanup)(void *obj);
+
+/*!
+ * \brief Supported Asterisk external application data types
+ */
+enum AST_AEAP_DATA_TYPE {
+	AST_AEAP_DATA_TYPE_NONE,
+	AST_AEAP_DATA_TYPE_BINARY,
+	AST_AEAP_DATA_TYPE_STRING,
+};
+
+/*!
+ * \brief Callbacks and other parameters used by an Asterisk external application object
+ */
+struct ast_aeap_params {
+	/*!
+	 * If true pass along error messages to the implementation.
+	 * Otherwise log it only, and consider it handled.
+	 */
+	unsigned int emit_error;
+
+	/*! The message type used for communication */
+	const struct ast_aeap_message_type *msg_type;
+
+	/*! Response handlers array */
+	const struct ast_aeap_message_handler *response_handlers;
+	/*! The number of response handlers */
+	uintmax_t response_handlers_size;
+
+	/*! Request handlers array */
+	const struct ast_aeap_message_handler *request_handlers;
+	/*! The number of request handlers */
+	uintmax_t request_handlers_size;
+
+	/*!
+	 * \brief Raised when binary data is received
+	 *
+	 * \param aeap An Asterisk external application object
+	 * \param buf The buffer containing binary data
+	 * \param size The size of the buffer
+	 */
+	void (*on_binary)(struct ast_aeap *aeap, const void *buf, intmax_t size);
+
+	/*!
+	 * \brief Raised when string data is received
+	 *
+	 * \param aeap An Asterisk external application object
+	 * \param buf The buffer containing string data
+	 * \param size The size/length of the string
+	 */
+	void (*on_string)(struct ast_aeap *aeap, const char *buf, intmax_t size);
+
+	/*!
+	 * \brief Raised when an error occurs during reading
+	 *
+	 * \note This is an AEAP transport level read error event
+	 *
+	 * \note When this event is triggered the client has also
+	 *       been disconnected.
+	 *
+	 * \param aeap An Asterisk external application object
+	 */
+	void (*on_error)(struct ast_aeap *aeap);
+};
+
+/*!
+ * \brief Create an Asterisk external application object
+ *
+ * \param type The type of underlying transport
+ * \param params Callbacks and other parameters to use
+ *
+ * \returns A new ao2 reference counted aeap object, or NULL on error
+ */
+struct ast_aeap *ast_aeap_create(const char *type, const struct ast_aeap_params *params);
+
+/*!
+ * \brief Create an Asterisk external application object by sorcery id
+ *
+ * \param id The sorcery id to lookup
+ * \param params Callbacks and other parameters to use
+ *
+ * \returns A new ao2 reference counted aeap object, or NULL on error
+ */
+struct ast_aeap *ast_aeap_create_by_id(const char *id, const struct ast_aeap_params *params);
+
+/*!
+ * \brief Connect to an external application
+ *
+ * \param aeap An Asterisk external application object
+ * \param url The url to connect to
+ * \param protocol A protocol to use
+ * \param timeout How long (in milliseconds) to attempt to connect (-1 equals infinite)
+ *
+ * \returns 0 if able to connect, -1 on error
+ */
+int ast_aeap_connect(struct ast_aeap *aeap, const char *url, const char *protocol, int timeout);
+
+/*!
+ * \brief Create and connect to an Asterisk external application by sorcery id
+ *
+ * \param id The sorcery id to lookup
+ * \param params Callbacks and other parameters to use
+ * \param timeout How long (in milliseconds) to attempt to connect (-1 equals infinite)
+ *
+ * \returns A new ao2 reference counted aeap object, or NULL on error
+ */
+struct ast_aeap *ast_aeap_create_and_connect_by_id(const char *id,
+	const struct ast_aeap_params *params, int timeout);
+
+/*!
+ * \brief Create and connect to an Asterisk external application
+ *
+ * \param type The type of client connection to make
+ * \param params Callbacks and other parameters to use
+ * \param url The url to connect to
+ * \param protocol A protocol to use
+ * \param timeout How long (in milliseconds) to attempt to connect (-1 equals infinite)
+ *
+ * \returns A new ao2 reference counted aeap object, or NULL on error
+ */
+struct ast_aeap *ast_aeap_create_and_connect(const char *type,
+	const struct ast_aeap_params *params, const char *url, const char *protocol, int timeout);
+
+/*!
+ * \brief Disconnect an Asterisk external application object
+ *
+ * \note Depending on the underlying transport this call may block
+ *
+ * \param aeap An Asterisk external application object
+ *
+ * \returns 0 on success, -1 on error
+ */
+int ast_aeap_disconnect(struct ast_aeap *aeap);
+
+/*!
+ * \brief Register a user data object
+ *
+ * \note The "cleanup" is called on un-register, if one is specified
+ *
+ * \param aeap An Asterisk external application object
+ * \param id The look up id for the object
+ * \param obj The user object to register
+ * \param cleanup Optional user object clean up callback
+ *
+ * \returns 0 on success, -1 on error
+ */
+int ast_aeap_user_data_register(struct ast_aeap *aeap, const char *id, void *obj,
+	ast_aeap_user_obj_cleanup cleanup);
+
+/*!
+ * \brief Un-register a user data object
+ *
+ * \note If specified on register, the "cleanup" callback is called during unregister.
+ *
+ * \param aeap An Asterisk external application object
+ * \param id The look up id for the object
+ */
+void ast_aeap_user_data_unregister(struct ast_aeap *aeap, const char *id);
+
+/*!
+ * \brief Retrieve a registered user data object by its id
+ *
+ * \note Depending on how it was registered the returned user data object's lifetime
+ *       may be managed by the given "aeap" object. If it was registered with a cleanup
+ *       handler that [potentially] frees it the caller of this function must ensure
+ *       it's done using the returned object before it's unregistered.
+ *
+ * \param aeap An Asterisk external application object
+ * \param id The look up id for the object
+ *
+ * \returns A user data object
+ */
+void *ast_aeap_user_data_object_by_id(struct ast_aeap *aeap, const char *id);
+
+/*!
+ * \brief Send a binary data to an external application
+ *
+ * \param aeap An Asterisk external application object
+ * \param buf Binary data to send
+ * \param size The size of the binary data
+ *
+ * \returns 0 on success, -1 on error
+ */
+int ast_aeap_send_binary(struct ast_aeap *aeap, const void *buf, uintmax_t size);
+
+/*!
+ * \brief Send a message to an external application
+ *
+ * \note "Steals" the given message reference, thus callers are not required to un-ref
+ *       the message object after calling this function.
+ *
+ * \param aeap An Asterisk external application object
+ * \param msg The message to send
+ *
+ * \returns 0 on success, -1 on error
+ */
+int ast_aeap_send_msg(struct ast_aeap *aeap, struct ast_aeap_message *msg);
+
+/*!
+ * \brief Parameters to be used when sending a transaction based message
+ */
+struct ast_aeap_tsx_params {
+	/*! The message to send */
+	struct ast_aeap_message *msg;
+	/*! The amount of time (in milliseconds) to wait for a received message */
+	int timeout;
+	/*! Optional callback raised when no message is received in an allotted time */
+	ast_aeap_on_timeout on_timeout;
+	/*! Whether or not to block the current thread, and wait for a received message */
+	int wait;
+	/*!
+	 * Optional user object to pass to handlers. User is responsible for object's lifetime
+	 * unless an obj_cleanup callback is specified that handles its cleanup (e.g. freeing
+	 * of memory).
+	 */
+	void *obj;
+	/*!
+	 * Optional user object cleanup callback. If specified, called upon "this" param's
+	 * destruction (including on error).
+	 */
+	ast_aeap_user_obj_cleanup obj_cleanup;
+};
+
+/*!
+ * \brief Send a transaction based message to an external application using the given parameters
+ *
+ * \note "Steals" the given message reference, thus callers are not required to un-ref
+ *       the message object after calling this function.
+ *
+ * \note Also handles cleaning up the user object if the obj_cleanup callback
+ *       is specified in "params".
+ *
+ * \param aeap An Asterisk external application object
+ * \param params (optional) Additional parameters to consider when sending. Heap allocation
+ *     not required.
+ *
+ * \returns 0 on success, -1 on error
+ */
+int ast_aeap_send_msg_tsx(struct ast_aeap *aeap, struct ast_aeap_tsx_params *params);
+
+#endif /* AST_RES_AEAP_H */
diff --git a/include/asterisk/res_aeap_message.h b/include/asterisk/res_aeap_message.h
new file mode 100644
index 0000000000000000000000000000000000000000..1fd532911bf888d851049ca8d932ca66b4e8ad10
--- /dev/null
+++ b/include/asterisk/res_aeap_message.h
@@ -0,0 +1,373 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ * \brief Asterisk External Application Protocol Message API
+ */
+
+#ifndef AST_AEAP_MESSAGE_H
+#define AST_AEAP_MESSAGE_H
+
+#include <stdint.h>
+
+#include "asterisk/res_aeap.h"
+
+struct ast_aeap_message;
+
+/*!
+ * \brief Message type virtual method table
+ */
+struct ast_aeap_message_type {
+	/*! The size of the message implementation type. Used for allocations. */
+	size_t type_size;
+	/*! The name of this type */
+	const char *type_name;
+	/*! The type to serialize to, and de-serialize from */
+	enum AST_AEAP_DATA_TYPE serial_type;
+
+	/*!
+	 * \brief Construct/Initialize a message object
+	 *
+	 * \param self The message object to initialize
+	 * \param params Other optional parameter(s) to possibly use
+	 *
+	 * \returns 0 on success, -1 on error
+	 */
+	int (*construct1)(struct ast_aeap_message *self, const void *params);
+
+	/*!
+	 * \brief Construct/Initialize a message object
+	 *
+	 * \param self The message object to initialize
+	 * \param msg_type The type of message (e.g. request or response)
+	 * \param name The name of the message
+	 * \param id The message id
+	 * \param params Other optional parameter(s) to possibly use
+	 *
+	 * \returns 0 on success, -1 on error
+	 */
+	int (*construct2)(struct ast_aeap_message *self, const char *msg_type, const char *name,
+		const char *id, const void *params);
+
+	/*!
+	 * \brief Destruct/Cleanup object resources
+	 *
+	 * \param self The message object being destructed
+	 */
+	void (*destruct)(struct ast_aeap_message *self);
+
+	/*!
+	 * \brief Deserialize the given buffer into a message object
+	 *
+	 * \param self The message object to deserialize into
+	 * \param buf The buffer to deserialize
+	 * \param size The size/length of the buffer
+	 *
+	 * \returns 0 on success, -1 on error
+	 */
+	int (*deserialize)(struct ast_aeap_message *self, const void *buf, intmax_t size);
+
+	/*!
+	 * \brief Serialize the message object into byte/char buffer
+	 *
+	 * \param self The message object to serialize
+	 * \param buf [out] The buffer to hold the "packed" data
+	 * \param size [out] The size of the data written to the buffer
+	 *
+	 * \returns 0 on success, -1 on error
+	 */
+	int (*serialize)(const struct ast_aeap_message *self, void **buf, intmax_t *size);
+
+	/*!
+	 * \brief Retrieve a message id
+	 *
+	 * \param self The message object
+	 *
+	 * \returns The message id
+	 */
+	const char *(*id)(const struct ast_aeap_message *self);
+
+	/*!
+	 * \brief Set a message id.
+	 *
+	 * \param self The message object
+	 * \param id The id to set
+	 *
+	 * \returns 0 on success, -1 on error
+	 */
+	int (*id_set)(struct ast_aeap_message *self, const char *id);
+
+	/*!
+	 * \brief Retrieve a message name
+	 *
+	 * \param self The message object
+	 *
+	 * \returns The message name
+	 */
+	const char *(*name)(const struct ast_aeap_message *self);
+
+	/*!
+	 * \brief Retrieve the core message data/body
+	 *
+	 * \param self This message object
+	 */
+	void *(*data)(struct ast_aeap_message *self);
+
+	/*!
+	 * \brief Retrieve whether or not this is a request message
+	 *
+	 * \param self The message object
+	 *
+	 * \returns True if message is a request, false otherwise
+	 */
+	int (*is_request)(const struct ast_aeap_message *self);
+
+	/*!
+	 * \brief Retrieve whether or not this is a response message
+	 *
+	 * \param self The message object
+	 *
+	 * \returns True if message is a response, false otherwise
+	 */
+	int (*is_response)(const struct ast_aeap_message *self);
+
+	/*!
+	 * \brief Retrieve the error message if it has one
+	 *
+	 * \param self The message object
+	 *
+	 * \returns The error message if available, or NULL
+	 */
+	const char *(*error_msg)(const struct ast_aeap_message *self);
+
+	/*!
+	 * \brief Set an error message
+	 *
+	 * \param self The message object
+	 * \param error_msg The error message string to set
+	 *
+	 * \returns 0 on success, -1 on error
+	 */
+	int (*error_msg_set)(struct ast_aeap_message *self, const char *error_msg);
+};
+
+/*!
+ * \brief Asterisk external application base message
+ */
+struct ast_aeap_message {
+	/*! The type virtual table */
+	const struct ast_aeap_message_type *type;
+};
+
+/*!
+ * \brief Retrieve the serial type a message type
+ *
+ * \param type A message type
+ *
+ * \returns The type's serial type
+ */
+enum AST_AEAP_DATA_TYPE ast_aeap_message_serial_type(const struct ast_aeap_message_type *type);
+
+/*!
+ * \brief Create an Asterisk external application message object
+ *
+ * \param type The type of message object to create
+ * \param params Any parameter(s) to pass to the type's constructor
+ *
+ * \returns An ao2 reference counted AEAP message object, or NULL on error
+ */
+struct ast_aeap_message *ast_aeap_message_create1(const struct ast_aeap_message_type *type,
+	const void *params);
+
+/*!
+ * \brief Create an Asterisk external application message object
+ *
+ * \param type The type of message object to create
+ * \param msg_type The type of message (e.g. request or response)
+ * \param name The name of the message
+ * \param id The message id
+ * \param params Other optional parameter(s) to possibly use
+ *
+ * \returns An ao2 reference counted AEAP message object, or NULL on error
+ */
+struct ast_aeap_message *ast_aeap_message_create2(const struct ast_aeap_message_type *type,
+	const char *msg_type, const char *name, const char *id, const void *params);
+
+/*!
+ * \brief Create an Asterisk external application request object
+ *
+ * \param type The type of message object to create
+ * \param name The name of the message
+ * \param id Optional id (if NULL an id is generated)
+ * \param params Other optional parameter(s) to possibly use
+ *
+ * \returns An ao2 reference counted AEAP request object, or NULL on error
+ */
+struct ast_aeap_message *ast_aeap_message_create_request(const struct ast_aeap_message_type *type,
+	const char *name, const char *id, const void *params);
+
+/*!
+ * \brief Create an Asterisk external application response object
+ *
+ * \param type The type of message object to create
+ * \param name The name of the message
+ * \param id Optional id
+ * \param params Other optional parameter(s) to possibly use
+ *
+ * \returns An ao2 reference counted AEAP response object, or NULL on error
+ */
+struct ast_aeap_message *ast_aeap_message_create_response(const struct ast_aeap_message_type *type,
+	const char *name, const char *id, const void *params);
+
+/*!
+ * \brief Create an Asterisk external application error response object
+ *
+ * \param type The type of message object to create
+ * \param name The name of the message
+ * \param id Optional id
+ * \param error_msg Error message to set
+ *
+ * \returns An ao2 reference counted AEAP response object, or NULL on error
+ */
+struct ast_aeap_message *ast_aeap_message_create_error(const struct ast_aeap_message_type *type,
+	const char *name, const char *id, const char *error_msg);
+
+/*!
+ * \brief Deserialize the given buffer into an Asterisk external application message object
+ *
+ * \param type The message type to create, and deserialize to
+ * \param buf The buffer to deserialize
+ * \param size The size/length of the buffer
+ *
+ * \returns An ao2 reference counted AEAP message object, or NULL on error
+ */
+struct ast_aeap_message *ast_aeap_message_deserialize(const struct ast_aeap_message_type *type,
+	const void *buf, intmax_t size);
+
+/*!
+ * \brief Serialize the given message object into a byte/char buffer
+ *
+ * \param message The message object to serialize
+ * \param buf [out] The buffer to hold the "packed" data
+ * \param size [out] The size of the data written to the buffer
+ *
+ * \returns 0 on success, -1 on error
+ */
+int ast_aeap_message_serialize(const struct ast_aeap_message *message,
+	void **buf, intmax_t *size);
+
+/*!
+ * \brief Retrieve a message id
+ *
+ * \param message A message object
+ *
+ * \returns The message id, or an empty string
+ */
+const char *ast_aeap_message_id(const struct ast_aeap_message *message);
+
+/*!
+ * \brief Set a message id.
+ *
+ * \param message A message object
+ * \param id The id to set
+ *
+ * \returns 0 on success, -1 on error
+ */
+int ast_aeap_message_id_set(struct ast_aeap_message *message, const char *id);
+
+/*!
+ * \brief Generate an id, and set it for the message
+ *
+ * \param message A message object
+ *
+ * \returns the generated id on success, or NULL on error
+ */
+const char *ast_aeap_message_id_generate(struct ast_aeap_message *message);
+
+/*!
+ * \brief Retrieve a message name
+ *
+ * \param message A message object
+ *
+ * \returns The message name, or an empty string
+ */
+const char *ast_aeap_message_name(const struct ast_aeap_message *message);
+
+/*!
+ * \brief Check whether or not a message's name matches the given one
+ *
+ * \note Case insensitive
+ *
+ * \param message A message object
+ * \param name The name to check against
+ *
+ * \returns True if matched, false otherwise
+ */
+int ast_aeap_message_is_named(const struct ast_aeap_message *message, const char *name);
+
+/*!
+ * \brief Retrieve the core message data/body
+ *
+ * \param message A message object
+ */
+void *ast_aeap_message_data(struct ast_aeap_message *message);
+
+/*!
+ * \brief Retrieve whether or not this is a request message
+ *
+ * \param message A message object
+ *
+ * \returns True if the message is a request, false otherwise
+ */
+int ast_aeap_message_is_request(const struct ast_aeap_message *message);
+
+/*!
+ * \brief Retrieve whether or not this is a response message
+ *
+ * \param message A message object
+ *
+ * \returns True if the message is a response, false otherwise
+ */
+int ast_aeap_message_is_response(const struct ast_aeap_message *message);
+
+/*!
+ * \brief Retrieve the error message if it has one
+ *
+ * \param message A message object
+ *
+ * \returns The error message if available, or NULL
+ */
+const char *ast_aeap_message_error_msg(const struct ast_aeap_message *message);
+
+/*!
+ * \brief Set an error message.
+ *
+ * \param message A message object
+ * \param error_msg The error string to set
+ *
+ * \returns 0 on success, -1 on error
+ */
+int ast_aeap_message_error_msg_set(struct ast_aeap_message *message,
+	const char *error_msg);
+
+/*!
+ * \brief Asterisk external application JSON message type
+ */
+extern const struct ast_aeap_message_type *ast_aeap_message_type_json;
+
+#endif /* AST_AEAP_MESSAGE_H */
diff --git a/include/asterisk/res_geolocation.h b/include/asterisk/res_geolocation.h
new file mode 100644
index 0000000000000000000000000000000000000000..2f88c80adcd06effb2ced915880c736cbc666314
--- /dev/null
+++ b/include/asterisk/res_geolocation.h
@@ -0,0 +1,413 @@
+ /*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef INCLUDE_ASTERISK_RES_GEOLOCATION_H_
+#define INCLUDE_ASTERISK_RES_GEOLOCATION_H_
+
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/sorcery.h"
+#include "asterisk/xml.h"
+#include "asterisk/optional_api.h"
+
+#define AST_GEOLOC_INVALID_VALUE -1
+
+enum ast_geoloc_pidf_element {
+	AST_PIDF_ELEMENT_NONE = 0,
+	AST_PIDF_ELEMENT_DEVICE,
+	AST_PIDF_ELEMENT_TUPLE,
+	AST_PIDF_ELEMENT_PERSON,
+	AST_PIDF_ELEMENT_LAST,
+};
+
+enum ast_geoloc_format {
+	AST_GEOLOC_FORMAT_NONE = 0,
+	AST_GEOLOC_FORMAT_CIVIC_ADDRESS,
+	AST_GEOLOC_FORMAT_GML,
+	AST_GEOLOC_FORMAT_URI,
+	AST_GEOLOC_FORMAT_LAST,
+};
+
+enum ast_geoloc_precedence {
+	AST_GEOLOC_PRECED_PREFER_INCOMING = 0,
+	AST_GEOLOC_PRECED_PREFER_CONFIG,
+	AST_GEOLOC_PRECED_DISCARD_INCOMING,
+	AST_GEOLOC_PRECED_DISCARD_CONFIG,
+};
+
+#define CONFIG_STR_TO_ENUM_DECL(_stem) int ast_geoloc_ ## _stem ## _str_to_enum(const char *str);
+CONFIG_STR_TO_ENUM_DECL(pidf_element)
+CONFIG_STR_TO_ENUM_DECL(format);
+CONFIG_STR_TO_ENUM_DECL(precedence);
+#define GEOLOC_ENUM_TO_NAME_DECL(_stem) const char * ast_geoloc_ ## _stem ## _to_name(int ix);
+GEOLOC_ENUM_TO_NAME_DECL(pidf_element)
+GEOLOC_ENUM_TO_NAME_DECL(format);
+GEOLOC_ENUM_TO_NAME_DECL(precedence);
+
+struct ast_geoloc_location {
+	SORCERY_OBJECT(details);
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(method);
+		AST_STRING_FIELD(location_source);
+	);
+	enum ast_geoloc_format format;
+	struct ast_variable *location_info;
+	struct ast_variable *confidence;
+};
+
+struct ast_geoloc_profile {
+	SORCERY_OBJECT(details);
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(location_reference);
+		AST_STRING_FIELD(notes);
+		AST_STRING_FIELD(method);
+		AST_STRING_FIELD(location_source);
+	);
+	enum ast_geoloc_pidf_element pidf_element;
+	enum ast_geoloc_precedence precedence;
+	int allow_routing_use;
+	struct ast_variable *location_refinement;
+	struct ast_variable *location_variables;
+	struct ast_variable *usage_rules;
+	int suppress_empty_ca_elements;
+	enum ast_geoloc_format format;
+	struct ast_variable *location_info;
+	struct ast_variable *confidence;
+};
+
+struct ast_geoloc_eprofile {
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(id);
+		AST_STRING_FIELD(location_reference);
+		AST_STRING_FIELD(location_source);
+		AST_STRING_FIELD(method);
+		AST_STRING_FIELD(notes);
+	);
+	enum ast_geoloc_pidf_element pidf_element;
+	enum ast_geoloc_precedence precedence;
+	int allow_routing_use;
+	enum ast_geoloc_format format;
+	struct ast_variable *location_info;
+	struct ast_variable *location_refinement;
+	struct ast_variable *location_variables;
+	struct ast_variable *effective_location;
+	struct ast_variable *usage_rules;
+	struct ast_variable *confidence;
+	int suppress_empty_ca_elements;
+};
+
+/*!
+ * \brief Check if res_geolocation is available
+ *
+ * \return 1 if available, 0 otherwise.
+ */
+AST_OPTIONAL_API(int, ast_geoloc_is_loaded,	(void), { return 0; });
+
+/*!
+ * \brief Retrieve a geolocation location object by id.
+ *
+ * \param id Location object id.
+ *
+ * \return Location object or NULL if not found.
+ */
+AST_OPTIONAL_API(struct ast_geoloc_location *, ast_geoloc_get_location,
+		 (const char *id),
+		 { return NULL; });
+
+/*!
+ * \brief Retrieve a geolocation profile by id.
+ *
+ * \param id profile id.
+ *
+ * \return Profile or NULL if not found.
+ */
+AST_OPTIONAL_API(struct ast_geoloc_profile *, ast_geoloc_get_profile,
+		 (const char *id),
+		 { return NULL; });
+
+/*!
+ * \brief Given a civicAddress code, check whether it's valid.
+ *
+ * \param code Pointer to the code to check
+ *
+ * \return 1 if valid, 0 otherwise.
+ */
+int ast_geoloc_civicaddr_is_code_valid(const char *code);
+
+enum ast_geoloc_validate_result {
+	AST_GEOLOC_VALIDATE_INVALID_VALUE = -1,
+	AST_GEOLOC_VALIDATE_SUCCESS = 0,
+	AST_GEOLOC_VALIDATE_MISSING_SHAPE,
+	AST_GEOLOC_VALIDATE_INVALID_SHAPE,
+	AST_GEOLOC_VALIDATE_INVALID_VARNAME,
+	AST_GEOLOC_VALIDATE_NOT_ENOUGH_VARNAMES,
+	AST_GEOLOC_VALIDATE_TOO_MANY_VARNAMES,
+};
+
+const char *ast_geoloc_validate_result_to_str(enum ast_geoloc_validate_result result);
+
+/*!
+ * \brief Validate that the names of the variables in the list are valid codes or synonyms
+ *
+ * \param varlist Variable list to check.
+ * \param[out] result Pointer to char * to receive failing item.
+ *
+ * \return result code.
+ */
+enum ast_geoloc_validate_result ast_geoloc_civicaddr_validate_varlist(
+	const struct ast_variable *varlist, const char **result);
+
+/*!
+ * \brief Validate that the variables in the list represent a valid GML shape
+ *
+ * \param varlist Variable list to check.
+ * \param[out] result Pointer to char * to receive failing item.
+ *
+ * \return result code.
+ */
+enum ast_geoloc_validate_result ast_geoloc_gml_validate_varlist(const struct ast_variable *varlist,
+	const char **result);
+
+
+/*!
+ * \brief Geolocation datastore Functions
+ * @{
+ */
+
+/*!
+ * \brief Create a geoloc datastore from a profile name
+ *
+ * \param profile_name The name of the profile to use.
+ *
+ * \return The datastore.
+ */
+struct ast_datastore *ast_geoloc_datastore_create_from_profile_name(const char *profile_name);
+
+/*!
+ * \brief Create a geoloc datastore from an effective profile.
+ *
+ * \param eprofile The effective profile to use.
+ *
+ * \return The datastore.
+ */
+struct ast_datastore *ast_geoloc_datastore_create_from_eprofile(
+	struct ast_geoloc_eprofile *eprofile);
+
+/*!
+ * \brief Create an empty geoloc datastore.
+ *
+ * \param id  An id to use for the datastore.
+ *
+ * \return The datastore.
+ */
+struct ast_datastore *ast_geoloc_datastore_create(const char *id);
+
+/*!
+ * \brief Retrieve a geoloc datastore's id.
+ *
+ * \param ds The datastore
+ *
+ * \return The datastore's id.
+ */
+const char *ast_geoloc_datastore_get_id(struct ast_datastore *ds);
+
+/*!
+ * \brief Add an eprofile to a datastore
+ *
+ * \param ds       The datastore
+ * \param eprofile The eprofile to add.
+ *
+ * \return The new number of eprofiles or -1 to indicate a failure.
+ */
+int ast_geoloc_datastore_add_eprofile(struct ast_datastore *ds,
+	struct ast_geoloc_eprofile *eprofile);
+
+/*!
+ * \brief Insert an eprofile to a datastore at the specified position
+ *
+ * \param ds       The datastore
+ * \param eprofile The eprofile to add.
+ * \param index    The position to insert at.  Existing eprofiles will
+ *                 be moved up to make room.
+ *
+ * \return The new number of eprofiles or -1 to indicate a failure.
+ */
+int ast_geoloc_datastore_insert_eprofile(struct ast_datastore *ds,
+	struct ast_geoloc_eprofile *eprofile, int index);
+
+/*!
+ * \brief Retrieves the number of eprofiles in the datastore
+ *
+ * \param ds The datastore
+ *
+ * \return The number of eprofiles.
+ */
+int ast_geoloc_datastore_size(struct ast_datastore *ds);
+
+/*!
+ * \brief Sets the inheritance flag on the datastore
+ *
+ * \param ds      The datastore
+ * \param inherit 1 to allow the datastore to be inherited by other channels
+ *                0 to prevent the datastore to be inherited by other channels
+ *
+ * \return 0 if successful, -1 otherwise.
+ */
+int ast_geoloc_datastore_set_inheritance(struct ast_datastore *ds, int inherit);
+
+/*!
+ * \brief Retrieve a specific eprofile from a datastore by index
+ *
+ * \param ds The datastore
+ * \param ix The index
+ *
+ * \return The effective profile ao2 object with its reference count bumped.
+ */
+struct ast_geoloc_eprofile *ast_geoloc_datastore_get_eprofile(struct ast_datastore *ds, int ix);
+
+/*!
+ * \brief Delete a specific eprofile from a datastore by index
+ *
+ * \param ds The datastore
+ * \param ix The index
+ *
+ * \return 0 if succesful, -1 otherwise.
+ */
+int ast_geoloc_datastore_delete_eprofile(struct ast_datastore *ds, int ix);
+
+/*!
+ * \brief Retrieves the geoloc datastore from a channel, if any
+ *
+ * \param chan Channel
+ *
+ * \return datastore if found, NULL otherwise.
+ */
+struct ast_datastore *ast_geoloc_datastore_find(struct ast_channel *chan);
+
+/*!
+ *  @}
+ */
+
+/*!
+ * \brief Geolocation Effective Profile Functions
+ * @{
+ */
+
+/*!
+ * \brief Allocate a new, empty effective profile.
+ *
+ * \param name The profile's name
+ *
+ * \return The effective profile ao2 object.
+ */
+struct ast_geoloc_eprofile *ast_geoloc_eprofile_alloc(const char *name);
+
+/*!
+ * \brief Duplicate an effective profile.
+ *
+ * \param src The eprofile to duplicate.
+ *
+ * \return The duplicated effective profile ao2 object.
+ */
+struct ast_geoloc_eprofile *ast_geoloc_eprofile_dup(struct ast_geoloc_eprofile *src);
+
+/*!
+ * \brief Allocate a new effective profile from an existing profile.
+ *
+ * \param profile The profile to use.
+ *
+ * \return The effective profile ao2 object.
+ */
+struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_profile(struct ast_geoloc_profile *profile);
+
+/*!
+ * \brief Allocate a new effective profile from an XML PIDF-LO document
+ *
+ * \param pidf_xmldoc       The ast_xml_doc to use.
+ * \param geoloc_uri        The URI that referenced this document.
+ * \param reference_string  An identifying string to use in error messages.
+ *
+ * \return The effective profile ao2 object.
+ */
+struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_pidf(
+	struct ast_xml_doc *pidf_xmldoc, const char *geoloc_uri, const char *reference_string);
+
+/*!
+ * \brief Allocate a new effective profile from a URI.
+ *
+ * \param uri               The URI to use.
+ * \param reference_string  An identifying string to use in error messages.
+ *
+ * \return The effective profile ao2 object.
+ */
+struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_uri(const char *uri,
+	const char *reference_string);
+
+/*!
+ * \brief Convert a URI eprofile to a URI string
+ *
+ * \param eprofile   Effective profile to convert
+ * \param chan       Channel to use to resolve variables
+ * \param buf        Pointer to ast_str pointer to use for work
+ * \param ref_string An identifying string to use in error messages.
+ *
+ * \return String representation of URI allocated from buf or NULL on failure
+ */
+const char *ast_geoloc_eprofile_to_uri(struct ast_geoloc_eprofile *eprofile,
+	struct ast_channel *chan, struct ast_str **buf, const char *ref_string);
+
+/*!
+ * \brief Convert a datastore containing eprofiles to a PIDF-LO document
+ *
+ * \param ds         Datastore containing effective profiles to convert
+ * \param chan       Channel to use to resolve variables
+ * \param buf        Pointer to ast_str pointer to use for work
+ * \param ref_string An identifying string to use in error messages.
+ *
+ * \return String representation PIDF-LO allocated from buf or NULL on failure.
+ */
+const char *ast_geoloc_eprofiles_to_pidf(struct ast_datastore *ds,
+	struct ast_channel *chan, struct ast_str **buf, const char * ref_string);
+
+/*!
+ * \brief Convert a single eprofile to a PIDF-LO document
+ *
+ * \param eprofile   Effective profile to convert
+ * \param chan       Channel to use to resolve variables
+ * \param buf        Pointer to ast_str pointer to use for work
+ * \param ref_string An identifying string to use in error messages.
+ *
+ * \return String representation PIDF-LO allocated from buf or NULL on failure.
+ */
+const char *ast_geoloc_eprofile_to_pidf(struct ast_geoloc_eprofile *eprofile,
+	struct ast_channel *chan, struct ast_str **buf, const char * ref_string);
+
+/*!
+ * \brief Refresh the effective profile with any changed info.
+ *
+ * \param eprofile The eprofile to refresh.
+ *
+ * \return 0 on success, any other value on error.
+ */
+int ast_geoloc_eprofile_refresh_location(struct ast_geoloc_eprofile *eprofile);
+
+/*!
+ *  @}
+ */
+
+#endif /* INCLUDE_ASTERISK_RES_GEOLOCATION_H_ */
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 5c65a5c276c4f73e2591ec3a6f18b1e81b8e1cf1..3e7efbf49ef29da779bf63181f24b9c59c3c6132 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -51,6 +51,11 @@
 #include "asterisk/stasis_endpoints.h"
 #include "asterisk/stream.h"
 
+#ifdef HAVE_PJSIP_TLS_TRANSPORT_RESTART
+/* Needed for knowing if the cert or priv key files changed */
+#include <sys/stat.h>
+#endif
+
 #define PJSIP_MINVERSION(m,n,p) (((m << 24) | (n << 16) | (p << 8)) >= PJ_VERSION_NUM)
 
 #ifndef PJSIP_EXPIRES_NOT_SPECIFIED
@@ -63,6 +68,9 @@
 #define PJSIP_EXPIRES_NOT_SPECIFIED	((pj_uint32_t)-1)
 #endif
 
+#define PJSTR_PRINTF_SPEC "%.*s"
+#define PJSTR_PRINTF_VAR(_v) ((int)(_v).slen), ((_v).ptr)
+
 /* Response codes from RFC8224 */
 #define AST_STIR_SHAKEN_RESPONSE_CODE_STALE_DATE 403
 #define AST_STIR_SHAKEN_RESPONSE_CODE_USE_IDENTITY_HEADER 428
@@ -79,6 +87,26 @@
 #define AST_STIR_SHAKEN_RESPONSE_STR_UNSUPPORTED_CREDENTIAL "Unsupported Credential"
 #define AST_STIR_SHAKEN_RESPONSE_STR_INVALID_IDENTITY_HEADER "Invalid Identity Header"
 
+/* ":12345" */
+#define COLON_PORT_STRLEN 6
+/*
+ * "<ipaddr>:<port>"
+ * PJ_INET6_ADDRSTRLEN includes the NULL terminator
+ */
+#define IP6ADDR_COLON_PORT_BUFLEN (PJ_INET6_ADDRSTRLEN + COLON_PORT_STRLEN)
+
+/*!
+ * \brief Fill a buffer with a pjsip transport's remote ip address and port
+ *
+ * \param _transport The pjsip_transport to use
+ * \param _dest The destination buffer of at least IP6ADDR_COLON_PORT_BUFLEN bytes
+ */
+#define AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(_transport, _dest) \
+	snprintf(_dest, IP6ADDR_COLON_PORT_BUFLEN, \
+		PJSTR_PRINTF_SPEC ":%d", \
+		PJSTR_PRINTF_VAR(_transport->remote_name.host), \
+		_transport->remote_name.port);
+
 /* Forward declarations of PJSIP stuff */
 struct pjsip_rx_data;
 struct pjsip_module;
@@ -97,6 +125,8 @@ struct pjsip_tpselector;
 
 AST_VECTOR(ast_sip_service_route_vector, char *);
 
+static const pj_str_t AST_PJ_STR_EMPTY = { "", 0 };
+
 /*!
  * \brief Structure for SIP transport information
  */
@@ -173,6 +203,24 @@ struct ast_sip_transport_state {
 	 * \since 17.0.0
 	 */
 	struct ast_sip_service_route_vector *service_routes;
+	/*!
+	 * Disregard RFC5922 7.2, and allow wildcard certs (TLS only)
+	 */
+	int allow_wildcard_certs;
+	/*!
+	 * If true, fail if server certificate cannot verify (TLS only)
+	 */
+	int verify_server;
+#ifdef HAVE_PJSIP_TLS_TRANSPORT_RESTART
+	/*!
+	 * The stats information for the certificate file, if configured
+	 */
+	struct stat cert_file_stat;
+	/*!
+	 * The stats information for the private key file, if configured
+	 */
+	struct stat privkey_file_stat;
+#endif
 };
 
 #define ast_sip_transport_is_nonlocal(transport_state, addr) \
@@ -291,6 +339,45 @@ struct ast_sip_nat_hook {
 	void (*outgoing_external_message)(struct pjsip_tx_data *tdata, struct ast_sip_transport *transport);
 };
 
+/*!
+ * \brief The kind of security negotiation
+ */
+enum ast_sip_security_negotiation {
+	/*! No security mechanism negotiation */
+	AST_SIP_SECURITY_NEG_NONE = 0,
+	/*! Use mediasec security mechanism negotiation */
+	AST_SIP_SECURITY_NEG_MEDIASEC,
+	/* Add RFC 3329 (sec-agree) mechanism negotiation in the future */
+};
+
+/*!
+ * \brief The security mechanism type
+ */
+enum ast_sip_security_mechanism_type {
+	AST_SIP_SECURITY_MECH_NONE = 0,
+	/* Use msrp-tls as security mechanism */
+	AST_SIP_SECURITY_MECH_MSRP_TLS,
+	/* Use sdes-srtp as security mechanism */
+	AST_SIP_SECURITY_MECH_SDES_SRTP,
+	/* Use dtls-srtp as security mechanism */
+	AST_SIP_SECURITY_MECH_DTLS_SRTP,
+	/* Add RFC 3329 (sec-agree) mechanisms like tle, digest, ipsec-ike in the future */
+};
+
+/*!
+ * \brief Structure representing a security mechanism as defined in RFC 3329
+ */
+struct ast_sip_security_mechanism {
+	/* Used to determine which security mechanism to use. */
+	enum ast_sip_security_mechanism_type type;
+	/* The preference of this security mechanism. The higher the value, the more preferred. */
+	float qvalue;
+	/* Optional mechanism parameters. */
+	struct ast_vector_string mechanism_parameters;
+};
+
+AST_VECTOR(ast_sip_security_mechanism_vector, struct ast_sip_security_mechanism *);
+
 /*!
  * \brief Contact associated with an address of record
  */
@@ -362,6 +449,13 @@ struct ast_sip_contact_status {
 	);
 	/*! The round trip time in microseconds */
 	int64_t rtt;
+	/*!
+	 * The security mechanism list of the contact (RFC 3329).
+	 * Stores the values of Security-Server headers in 401/421/494 responses to an
+	 * in-dialog request or successful outbound registration which will be used to
+	 * set the Security-Verify headers of all subsequent requests to the contact.
+	 */
+	struct ast_sip_security_mechanism_vector security_mechanisms;
 	/*! Current status for a contact (default - unavailable) */
 	enum ast_sip_contact_status_type status;
 	/*! Last status for a contact (default - unavailable) */
@@ -421,6 +515,20 @@ struct ast_sip_contact_wrapper {
 	struct ast_sip_contact *contact;
 };
 
+/*!
+ * \brief 100rel modes for SIP endpoints
+ */
+enum ast_sip_100rel_mode {
+	/*! Do not support 100rel. (no) */
+	AST_SIP_100REL_UNSUPPORTED = 0,
+	/*! As UAC, indicate 100rel support in Supported header. (yes) */
+	AST_SIP_100REL_SUPPORTED,
+	/*! As UAS, send 1xx responses reliably, if the UAC indicated its support. Otherwise same as AST_SIP_100REL_SUPPORTED. (peer_supported) */
+	AST_SIP_100REL_PEER_SUPPORTED,
+	/*! Require the use of 100rel. (required) */
+	AST_SIP_100REL_REQUIRED,
+};
+
 /*!
  * \brief DTMF modes for SIP endpoints
  */
@@ -957,6 +1065,10 @@ struct ast_sip_endpoint {
 	unsigned int suppress_q850_reason_headers;
 	/*! Ignore 183 if no SDP is present */
 	unsigned int ignore_183_without_sdp;
+	/*! Type of security negotiation to use (RFC 3329). */
+	enum ast_sip_security_negotiation security_negotiation;
+	/*! Client security mechanisms (RFC 3329). */
+	struct ast_sip_security_mechanism_vector security_mechanisms;
 	/*! Set which STIR/SHAKEN behaviors we want on this endpoint */
 	unsigned int stir_shaken;
 	/*! Should we authenticate OPTIONS requests per RFC 3261? */
@@ -965,9 +1077,18 @@ struct ast_sip_endpoint {
 	unsigned int mediasec;
 	/*! Call waiting enabled flag */
 	unsigned int call_waiting_enabled;
-        
 	/*! Security mechanisms supported by peer */
-	AST_LIST_HEAD_NOLOCK(, security_mechanism) security_mechanisms;
+	AST_LIST_HEAD_NOLOCK(, security_mechanism) secur_mechanisms;
+	/*! The name of the geoloc profile to apply when Asterisk receives a call from this endpoint */
+	AST_STRING_FIELD_EXTENDED(geoloc_incoming_call_profile);
+	/*! The name of the geoloc profile to apply when Asterisk sends a call to this endpoint */
+	AST_STRING_FIELD_EXTENDED(geoloc_outgoing_call_profile);
+	/*! The context to use for overlap dialing, if different from the endpoint's context */
+	AST_STRING_FIELD_EXTENDED(overlap_context);
+	/*! 100rel mode to use with this endpoint */
+	enum ast_sip_100rel_mode rel100;
+	/*! Send Advice-of-Charge messages */
+	unsigned int send_aoc;
 };
 
 /*! URI parameter for symmetric transport */
@@ -991,8 +1112,8 @@ extern pjsip_media_type pjsip_media_type_text_plain;
 /*!
  * \brief Compare pjsip media types
  *
- * \param pjsip_media_type a
- * \param pjsip_media_type b
+ * \param a the first media type
+ * \param b the second media type
  * \retval 1 Media types are equal
  * \retval 0 Media types are not equal
  */
@@ -1008,6 +1129,100 @@ int ast_sip_are_media_types_equal(pjsip_media_type *a, pjsip_media_type *b);
  */
 int ast_sip_is_media_type_in(pjsip_media_type *a, ...) attribute_sentinel;
 
+/*!
+ * \brief Add security headers to transmission data
+ *
+ * \param security_mechanisms Vector of security mechanisms.
+ * \param header_name The header name under which to add the security mechanisms.
+ * One of Security-Client, Security-Server, Security-Verify.
+ * \param add_qval If zero, don't add the q-value to the header.
+ * \param tdata The transmission data.
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+int ast_sip_add_security_headers(struct ast_sip_security_mechanism_vector *security_mechanisms,
+		const char *header_name, int add_qval, pjsip_tx_data *tdata);
+
+/*!
+ * \brief Append to security mechanism vector from SIP header
+ *
+ * \param hdr The header of the security mechanisms.
+ * \param security_mechanisms Vector of security mechanisms to append to.
+ * Header name must be one of Security-Client, Security-Server, Security-Verify.
+ */
+void ast_sip_header_to_security_mechanism(const pjsip_generic_string_hdr *hdr,
+		struct ast_sip_security_mechanism_vector *security_mechanisms);
+
+/*!
+ * \brief Initialize security mechanism vector from string of security mechanisms.
+ *
+ * \param security_mechanism Pointer to vector of security mechanisms to initialize.
+ * \param value String of security mechanisms as defined in RFC 3329.
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+int ast_sip_security_mechanism_vector_init(struct ast_sip_security_mechanism_vector *security_mechanism, const char *value);
+
+/*!
+ * \brief Removes all headers of a specific name and value from a pjsip_msg.
+ *
+ * \param msg PJSIP message from which to remove headers.
+ * \param hdr_name Name of the header to remove.
+ * \param value Optional string value of the header to remove.
+ * If NULL, remove all headers of given hdr_name.
+ */
+void ast_sip_remove_headers_by_name_and_value(pjsip_msg *msg, const pj_str_t *hdr_name, const char* value);
+
+/*!
+ * \brief Duplicate a security mechanism.
+ *
+ * \param dst Security mechanism to duplicate to.
+ * \param src Security mechanism to duplicate.
+ */
+void ast_sip_security_mechanisms_vector_copy(struct ast_sip_security_mechanism_vector *dst,
+	const struct ast_sip_security_mechanism_vector *src);
+
+/*!
+ * \brief Free contents of a security mechanism vector.
+ *
+ * \param security_mechanisms Vector whose contents are to be freed
+ */
+void ast_sip_security_mechanisms_vector_destroy(struct ast_sip_security_mechanism_vector *security_mechanisms);
+
+/*!
+ * \brief Allocate a security mechanism from a string.
+ *
+ * \param security_mechanism Pointer-pointer to the security mechanism to allocate.
+ * \param value The security mechanism string as defined in RFC 3329 (section 2.2)
+ *				in the form <mechanism_name>;q=<q_value>;<mechanism_parameters>
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+int ast_sip_str_to_security_mechanism(struct ast_sip_security_mechanism **security_mechanism, const char *value);
+
+/*!
+ * \brief Writes the security mechanisms of an endpoint into a buffer as a string and returns the buffer.
+ *
+ * \note The buffer must be freed by the caller.
+ *
+ * \param endpoint Pointer to endpoint.
+ * \param add_qvalue If non-zero, the q-value is printed as well
+ * \param buf The buffer to write the string into
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+int ast_sip_security_mechanisms_to_str(const struct ast_sip_security_mechanism_vector *security_mechanisms, int add_qvalue, char **buf);
+
+/*!
+ * \brief Set the security negotiation based on a given string.
+ *
+ * \param security_negotiation Security negotiation enum to set.
+ * \param val String that represents a security_negotiation value.
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+int ast_sip_set_security_negotiation(enum ast_sip_security_negotiation *security_negotiation, const char *val);
+
 /*!
  * \brief Initialize an auth vector with the configured values.
  *
@@ -2468,6 +2683,17 @@ int ast_sip_set_outbound_proxy(pjsip_tx_data *tdata, const char *proxy);
  */
 int ast_sip_add_header(pjsip_tx_data *tdata, const char *name, const char *value);
 
+/*!
+ * \brief Add a header to an outbound SIP message, returning a pointer to the header
+ *
+ * \param tdata The message to add the header to
+ * \param name The header name
+ * \param value The header value
+ * \return The pjsip_generic_string_hdr * added.
+ */
+pjsip_generic_string_hdr *ast_sip_add_header2(pjsip_tx_data *tdata,
+	const char *name, const char *value);
+
 /*!
  * \brief Add a body to an outbound SIP message
  *
@@ -3084,6 +3310,13 @@ int ast_sip_get_mwi_tps_queue_low(void);
  */
 unsigned int ast_sip_get_mwi_disable_initial_unsolicited(void);
 
+/*!
+ * \brief Retrieve the global setting 'allow_sending_180_after_183'.
+ *
+ * \retval non zero if disable.
+ */
+unsigned int ast_sip_get_allow_sending_180_after_183(void);
+
 /*!
  * \brief Retrieve the global setting 'use_callerid_contact'.
  * \since 13.24.0
@@ -3410,6 +3643,25 @@ struct ast_sip_service_route_vector *ast_sip_service_route_vector_alloc(void);
  */
 void ast_sip_service_route_vector_destroy(struct ast_sip_service_route_vector *service_routes);
 
+/*!
+ * \brief Set the ID for a connected line update
+ *
+ * \retval -1 on failure, 0 on success
+ */
+int ast_sip_set_id_connected_line(struct pjsip_rx_data *rdata, struct ast_party_id *id);
+
+/*!
+ * \brief Set the ID from an INVITE
+ *
+ * \param rdata
+ * \param id ID structure to fill
+ * \param default_id Default ID structure with data to use (for non-trusted endpoints)
+ * \param trust_inbound Whether or not the endpoint is trusted (controls whether PAI or RPID can be used)
+ *
+ * \retval -1 on failure, 0 on success
+ */
+int ast_sip_set_id_from_invite(struct pjsip_rx_data *rdata, struct ast_party_id *id, struct ast_party_id *default_id, int trust_inbound);
+
 /*!
  * \brief Set name and number information on an identity header.
  *
@@ -3574,6 +3826,7 @@ enum ast_transport_monitor_reg {
 
 /*!
  * \brief Register a reliable transport shutdown monitor callback.
+ * \deprecated Replaced with ast_sip_transport_monitor_register_key().
  * \since 13.20.0
  *
  * \param transport Transport to monitor for shutdown.
@@ -3591,8 +3844,29 @@ enum ast_transport_monitor_reg {
 enum ast_transport_monitor_reg ast_sip_transport_monitor_register(pjsip_transport *transport,
 	ast_transport_monitor_shutdown_cb cb, void *ao2_data);
 
+/*!
+ * \brief Register a reliable transport shutdown monitor callback.
+ *
+ * \param transport_key Key for the transport to monitor for shutdown.
+ *                      Create the key with AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR.
+ * \param cb Who to call when transport is shutdown.
+ * \param ao2_data Data to pass with the callback.
+ *
+ * \note The data object passed will have its reference count automatically
+ * incremented by this call and automatically decremented after the callback
+ * runs or when the callback is unregistered.
+ *
+ * There is no checking for duplicate registrations.
+ *
+ * \return enum ast_transport_monitor_reg
+ */
+enum ast_transport_monitor_reg ast_sip_transport_monitor_register_key(
+	const char *transport_key, ast_transport_monitor_shutdown_cb cb,
+	void *ao2_data);
+
 /*!
  * \brief Register a reliable transport shutdown monitor callback replacing any duplicate.
+ * \deprecated Replaced with ast_sip_transport_monitor_register_replace_key().
  * \since 13.26.0
  * \since 16.3.0
  *
@@ -3614,8 +3888,32 @@ enum ast_transport_monitor_reg ast_sip_transport_monitor_register(pjsip_transpor
 enum ast_transport_monitor_reg ast_sip_transport_monitor_register_replace(pjsip_transport *transport,
 	ast_transport_monitor_shutdown_cb cb, void *ao2_data, ast_transport_monitor_data_matcher matches);
 
+/*!
+ * \brief Register a reliable transport shutdown monitor callback replacing any duplicate.
+ *
+ * \param transport_key Key for the transport to monitor for shutdown.
+ *                      Create the key with AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR.
+ * \param cb Who to call when transport is shutdown.
+ * \param ao2_data Data to pass with the callback.
+ * \param matches Matcher function that returns true if data matches a previously
+ *                registered data object
+ *
+ * \note The data object passed will have its reference count automatically
+ * incremented by this call and automatically decremented after the callback
+ * runs or when the callback is unregistered.
+ *
+ * This function checks for duplicates, and overwrites/replaces the old monitor
+ * with the given one.
+ *
+ * \return enum ast_transport_monitor_reg
+ */
+enum ast_transport_monitor_reg ast_sip_transport_monitor_register_replace_key(
+	const char *transport_key, ast_transport_monitor_shutdown_cb cb,
+	void *ao2_data, ast_transport_monitor_data_matcher matches);
+
 /*!
  * \brief Unregister a reliable transport shutdown monitor
+ * \deprecated Replaced with ast_sip_transport_monitor_unregister_key().
  * \since 13.20.0
  *
  * \param transport Transport to monitor for shutdown.
@@ -3631,6 +3929,23 @@ enum ast_transport_monitor_reg ast_sip_transport_monitor_register_replace(pjsip_
 void ast_sip_transport_monitor_unregister(pjsip_transport *transport,
 	ast_transport_monitor_shutdown_cb cb, void *data, ast_transport_monitor_data_matcher matches);
 
+/*!
+ * \brief Unregister a reliable transport shutdown monitor
+ *
+ * \param transport_key Key for the transport to monitor for shutdown.
+ *                      Create the key with AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR.
+ * \param cb The callback that was used for the original register.
+ * \param data Data to pass to the matcher. May be NULL and does NOT need to be an ao2 object.
+ *             If NULL, all monitors with the provided callback are unregistered.
+ * \param matches Matcher function that returns true if data matches the previously
+ *                registered data object.  If NULL, a simple pointer comparison is done.
+ *
+ * \note The data object passed into the original register will have its reference count
+ * automatically decremented.
+ */
+void ast_sip_transport_monitor_unregister_key(const char *transport_key,
+	ast_transport_monitor_shutdown_cb cb, void *data, ast_transport_monitor_data_matcher matches);
+
 /*!
  * \brief Unregister a transport shutdown monitor from all reliable transports
  * \since 13.20.0
@@ -3670,4 +3985,95 @@ void ast_sip_transport_state_register(struct ast_sip_tpmgr_state_callback *eleme
  */
 void ast_sip_transport_state_unregister(struct ast_sip_tpmgr_state_callback *element);
 
+/*!
+ * \brief Check whether a pjsip_uri is SIP/SIPS or not
+ * \since 16.28.0
+ *
+ * \param uri The pjsip_uri to check
+ *
+ * \retval 1 if true
+ * \retval 0 if false
+ */
+int ast_sip_is_uri_sip_sips(pjsip_uri *uri);
+
+/*!
+ * \brief Check whether a pjsip_uri is allowed or not
+ * \since 16.28.0
+ *
+ * \param uri The pjsip_uri to check
+ *
+ * \retval 1 if allowed
+ * \retval 0 if not allowed
+ */
+int ast_sip_is_allowed_uri(pjsip_uri *uri);
+
+/*!
+ * \brief Get the user portion of the pjsip_uri
+ * \since 16.28.0
+ *
+ * \param uri The pjsip_uri to get the user from
+ *
+ * \note This function will check what kind of URI it receives and return
+ * the user based off of that
+ *
+ * \return User string or empty string if not present
+ */
+const pj_str_t *ast_sip_pjsip_uri_get_username(pjsip_uri *uri);
+
+/*!
+ * \brief Get the host portion of the pjsip_uri
+ * \since 16.28.0
+ *
+ * \param uri The pjsip_uri to get the host from
+ *
+ * \note This function will check what kind of URI it receives and return
+ * the host based off of that
+ *
+ * \return Host string or empty string if not present
+ */
+const pj_str_t *ast_sip_pjsip_uri_get_hostname(pjsip_uri *uri);
+
+/*!
+ * \brief Find an 'other' SIP/SIPS URI parameter by name
+ * \since 16.28.0
+ *
+ * A convenience function to find a named parameter from a SIP/SIPS URI. This
+ * function will not find the following standard SIP/SIPS URI parameters which
+ * are stored separately by PJSIP:
+ *
+ * \li `user`
+ * \li `method`
+ * \li `transport`
+ * \li `ttl`
+ * \li `lr`
+ * \li `maddr`
+ *
+ * \param uri The pjsip_uri to get the parameter from
+ * \param param_str The name of the parameter to find
+ *
+ * \note This function will check what kind of URI it receives and return
+ * the parameter based off of that
+ *
+ * \return Find parameter or NULL if not present
+ */
+struct pjsip_param *ast_sip_pjsip_uri_get_other_param(pjsip_uri *uri, const pj_str_t *param_str);
+
+/*!
+ * \brief Retrieve the system setting 'all_codecs_on_empty_reinvite'.
+ *
+ * \retval non zero if we should return all codecs on empty re-INVITE
+ */
+unsigned int ast_sip_get_all_codecs_on_empty_reinvite(void);
+
+
+/*!
+ * \brief Convert SIP hangup causes to Asterisk hangup causes
+ *
+ * \param cause SIP cause
+ *
+ * \retval matched cause code from causes.h
+ */
+const int ast_sip_hangup_sip2cause(int cause);
+
+
 #endif /* _RES_PJSIP_H */
diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h
index 4c1a96b2d6d1f7fc8dd7ecde4c539fea793e057d..1e2943090ae215044098f179da46632b9a4788ac 100644
--- a/include/asterisk/res_pjsip_session.h
+++ b/include/asterisk/res_pjsip_session.h
@@ -33,6 +33,8 @@
 /* Needed for pjmedia_sdp_session and pjsip_inv_session */
 #include <pjsip_ua.h>
 
+/* Needed for ast_sip_security_mechanism_vector */
+#include "asterisk/res_pjsip.h"
 
 /* Forward declarations */
 struct ast_sip_endpoint;
diff --git a/include/asterisk/res_stir_shaken.h b/include/asterisk/res_stir_shaken.h
index ece99b52a04ebb25596613c6342d5326d6dcaea5..540f988c44ddfb80896a27f4b1eb570d6e1e6cb3 100644
--- a/include/asterisk/res_stir_shaken.h
+++ b/include/asterisk/res_stir_shaken.h
@@ -156,7 +156,7 @@ struct ast_stir_shaken_payload *ast_stir_shaken_verify2(const char *header, cons
  * \param signature The payload signature
  * \param algorithm The signature algorithm
  * \param public_cert_url The public key URL
- * \param failure_code Additional failure information
+ * \param failure Additional failure information
  * \param profile The stir_shaken_profile
  *
  * \retval ast_stir_shaken_payload on success
diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h
index df549f8a6a358c667b86271ebcbe9bb4470098cb..164cfec7649e7f439ef45997159e743990b39864 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -174,6 +174,8 @@ enum ast_rtp_instance_stat_field {
 	AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS,
 	/*! Retrieve quality information about round trip time */
 	AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT,
+	/*! Retrieve quality information about Media Experience Score */
+	AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES,
 };
 
 /*! Statistics that can be retrieved from an RTP instance */
@@ -250,6 +252,29 @@ enum ast_rtp_instance_stat {
 	AST_RTP_INSTANCE_STAT_TXOCTETCOUNT,
 	/*! Retrieve number of octets received */
 	AST_RTP_INSTANCE_STAT_RXOCTETCOUNT,
+
+	/*! Retrieve ALL statistics relating to Media Experience Score */
+	AST_RTP_INSTANCE_STAT_COMBINED_MES,
+	/*! Retrieve MES on transmitted packets */
+	AST_RTP_INSTANCE_STAT_TXMES,
+	/*! Retrieve MES on received packets */
+	AST_RTP_INSTANCE_STAT_RXMES,
+	/*! Retrieve maximum MES on remote side */
+	AST_RTP_INSTANCE_STAT_REMOTE_MAXMES,
+	/*! Retrieve minimum MES on remote side */
+	AST_RTP_INSTANCE_STAT_REMOTE_MINMES,
+	/*! Retrieve average MES on remote side */
+	AST_RTP_INSTANCE_STAT_REMOTE_NORMDEVMES,
+	/*! Retrieve standard deviation MES on remote side */
+	AST_RTP_INSTANCE_STAT_REMOTE_STDEVMES,
+	/*! Retrieve maximum MES on local side */
+	AST_RTP_INSTANCE_STAT_LOCAL_MAXMES,
+	/*! Retrieve minimum MES on local side */
+	AST_RTP_INSTANCE_STAT_LOCAL_MINMES,
+	/*! Retrieve average MES on local side */
+	AST_RTP_INSTANCE_STAT_LOCAL_NORMDEVMES,
+	/*! Retrieve standard deviation MES on local side */
+	AST_RTP_INSTANCE_STAT_LOCAL_STDEVMES,
 };
 
 enum ast_rtp_instance_rtcp {
@@ -431,6 +456,27 @@ struct ast_rtp_instance_stats {
 	unsigned int txoctetcount;
 	/*! Number of octets received */
 	unsigned int rxoctetcount;
+
+	/*! Media Experience Score on transmitted packets */
+	double txmes;
+	/*! Media Experience Score on received packets */
+	double rxmes;
+	/*! Maximum MES on remote side */
+	double remote_maxmes;
+	/*! Minimum MES on remote side */
+	double remote_minmes;
+	/*! Average MES on remote side */
+	double remote_normdevmes;
+	/*! Standard deviation MES on remote side */
+	double remote_stdevmes;
+	/*! Maximum MES on local side */
+	double local_maxmes;
+	/*! Minimum MES on local side */
+	double local_minmes;
+	/*! Average MES on local side */
+	double local_normdevmes;
+	/*! Standard deviation MES on local side */
+	double local_stdevmes;
 };
 
 #define AST_RTP_STAT_SET(current_stat, combined, placement, value) \
@@ -1722,7 +1768,7 @@ void ast_rtp_codecs_payload_formats(struct ast_rtp_codecs *codecs, struct ast_fo
  * Example usage:
  *
  * \code
- * int payload = ast_rtp_codecs_payload_code(&codecs, 1, ast_format_set(&tmp_fmt, AST_FORMAT_ULAW, 0), 0);
+ * int payload = ast_rtp_codecs_payload_code(&codecs, 1, ast_format_ulaw, 0);
  * \endcode
  *
  * This looks for the numerical payload for ULAW in the codecs structure.
@@ -1791,7 +1837,7 @@ int ast_rtp_codecs_find_payload_code(struct ast_rtp_codecs *codecs, int payload)
  * Example usage:
  *
  * \code
- * const char *subtype = ast_rtp_lookup_mime_subtype2(1, ast_format_set(&tmp_fmt, AST_FORMAT_ULAW, 0), 0, 0);
+ * const char *subtype = ast_rtp_lookup_mime_subtype2(1, ast_format_ulaw, 0, 0);
  * \endcode
  *
  * This looks up the mime subtype for the ULAW format.
@@ -1819,8 +1865,8 @@ const char *ast_rtp_lookup_mime_subtype2(const int asterisk_format,
  * char buf[256] = "";
  * struct ast_format tmp_fmt;
  * struct ast_format_cap *cap = ast_format_cap_alloc_nolock();
- * ast_format_cap_append(cap, ast_format_set(&tmp_fmt, AST_FORMAT_ULAW, 0));
- * ast_format_cap_append(cap, ast_format_set(&tmp_fmt, AST_FORMAT_GSM, 0));
+ * ast_format_cap_append(cap, ast_format_ulaw, 0);
+ * ast_format_cap_append(cap, ast_format_ulaw, 0);
  * char *mime = ast_rtp_lookup_mime_multiple2(&buf, sizeof(buf), cap, 0, 1, 0);
  * ast_format_cap_destroy(cap);
  * \endcode
@@ -2223,7 +2269,7 @@ char *ast_rtp_instance_get_quality(struct ast_rtp_instance *instance, enum ast_r
  *
  * \code
  * struct ast_format tmp_fmt;
- * ast_rtp_instance_set_read_format(instance, ast_format_set(&tmp_fmt, AST_FORMAT_ULAW, 0));
+ * ast_rtp_instance_set_read_format(instance, ast_format_ulaw);
  * \endcode
  *
  * This requests that the RTP engine provide audio frames in the ULAW format.
@@ -2245,7 +2291,7 @@ int ast_rtp_instance_set_read_format(struct ast_rtp_instance *instance, struct a
  *
  * \code
  * struct ast_format tmp_fmt;
- * ast_rtp_instance_set_write_format(instance, ast_format_set(&tmp_fmt, AST_FORMAT_ULAW, 0));
+ * ast_rtp_instance_set_write_format(instance, ast_format_ulaw);
  * \endcode
  *
  * This tells the underlying RTP engine that audio frames will be provided to it in ULAW format.
@@ -2863,6 +2909,10 @@ uintmax_t ast_debug_category_ice_id(void);
 #define ast_debug_rtp(sublevel, ...) \
 	ast_debug_category(sublevel, AST_DEBUG_CATEGORY_RTP,  __VA_ARGS__)
 
+/* Allow logging of RTP? */
+#define ast_debug_rtp_is_allowed \
+	ast_debug_category_is_allowed(AST_LOG_CATEGORY_ENABLED, AST_DEBUG_CATEGORY_RTP)
+
 /* Allow logging of RTP packets? */
 #define ast_debug_rtp_packet_is_allowed \
 	ast_debug_category_is_allowed(AST_LOG_CATEGORY_ENABLED, AST_DEBUG_CATEGORY_RTP_PACKET)
@@ -2876,6 +2926,10 @@ uintmax_t ast_debug_category_ice_id(void);
 #define ast_debug_rtcp(sublevel, ...) \
 	ast_debug_category(sublevel, AST_DEBUG_CATEGORY_RTCP, __VA_ARGS__)
 
+/* Allow logging of RTCP? */
+#define ast_debug_rtcp_is_allowed \
+	ast_debug_category_is_allowed(AST_LOG_CATEGORY_ENABLED, AST_DEBUG_CATEGORY_RTCP)
+
 /* Allow logging of RTCP packets? */
 #define ast_debug_rtcp_packet_is_allowed \
 	ast_debug_category_is_allowed(AST_LOG_CATEGORY_ENABLED, AST_DEBUG_CATEGORY_RTCP_PACKET)
diff --git a/include/asterisk/speech.h b/include/asterisk/speech.h
index 681c536019c1f67f031d3f9d8f1be96e1849503f..ec5abaf3c382c7f9bf714d582fab04037f192aa8 100644
--- a/include/asterisk/speech.h
+++ b/include/asterisk/speech.h
@@ -158,6 +158,12 @@ int ast_speech_unregister(const char *engine_name);
 /*! \brief Unregister a speech recognition engine */
 struct ast_speech_engine *ast_speech_unregister2(const char *engine_name);
 
+/*! \brief Retrieve a speech recognition engine */
+struct ast_speech_engine *ast_speech_find_engine(const char *engine_name);
+/*! \brief Unregister all speech recognition engines told to by callback */
+void ast_speech_unregister_engines(
+	int (*should_unregister)(const struct ast_speech_engine *engine, void *data), void *data,
+	void (*on_unregistered)(void *obj));
 
 #if defined(__cplusplus) || defined(c_plusplus)
 }
diff --git a/include/asterisk/stasis_channels.h b/include/asterisk/stasis_channels.h
index 6cf08ca5cd1709697ea7ac1420286ba2e8d6484f..5a754f6715d532903c2966053a6bfb5ddc137384 100644
--- a/include/asterisk/stasis_channels.h
+++ b/include/asterisk/stasis_channels.h
@@ -112,6 +112,7 @@ struct ast_channel_snapshot_base {
 	);
 	struct timeval creationtime; /*!< The time of channel creation */
 	int tech_properties;         /*!< Properties of the channel's technology */
+	AST_STRING_FIELD_EXTENDED(protocol_id); /*!< Channel driver protocol id (i.e. Call-ID for chan_sip/chan_pjsip) */
 };
 
 /*!
diff --git a/include/asterisk/stringfields.h b/include/asterisk/stringfields.h
index edf479d75b4468471e8a6bf4d6e90dd201f2bf42..94ba756e98ad9d533dffe6dcfb22b68be72689d0 100644
--- a/include/asterisk/stringfields.h
+++ b/include/asterisk/stringfields.h
@@ -355,10 +355,11 @@ enum ast_stringfield_cleanup_type {
   \retval zero on success
   \retval non-zero on failure
 */
+
 #define ast_string_field_init(x, size) \
 ({ \
 	int __res__ = -1; \
-	if (((void *)(x)) != NULL) { \
+	if (((void *)(x)) != (void *)NULL) { \
 		__res__ = __ast_string_field_init(&(x)->__field_mgr, &(x)->__field_mgr_pool, size, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
 	} \
 	__res__ ; \
@@ -373,7 +374,7 @@ enum ast_stringfield_cleanup_type {
 #define ast_string_field_free_memory(x)	 \
 ({ \
 	int __res__ = -1; \
-	if (((void *)(x)) != NULL) { \
+	if (((void *)(x)) != (void *)NULL) { \
 		__res__ = __ast_string_field_free_memory(&(x)->__field_mgr, &(x)->__field_mgr_pool, \
 			AST_STRINGFIELD_DESTROY, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
 	} \
@@ -400,7 +401,7 @@ int __ast_string_field_free_memory(struct ast_string_field_mgr *mgr,
 #define ast_string_field_init_extended(x, field) \
 ({ \
 	int __res__ = -1; \
-	if (((void *)(x)) != NULL) { \
+	if (((void *)(x)) != (void *)NULL) { \
 		ast_string_field *non_const = (ast_string_field *)&(x)->field; \
 		*non_const = __ast_string_field_empty; \
 		__res__ = AST_VECTOR_APPEND(&(x)->__field_mgr.string_fields, non_const); \
@@ -474,7 +475,7 @@ void __ast_string_field_release_active(struct ast_string_field_pool *pool_head,
 #define ast_string_field_ptr_set(x, ptr, data) \
 ({ \
 	int __res__ = -1; \
-	if (((void *)(x)) != NULL) { \
+	if (((void *)(x)) != (void *)NULL) { \
 		__res__ = ast_string_field_ptr_set_by_fields((x)->__field_mgr_pool, (x)->__field_mgr, ptr, data); \
 	} \
 	__res__; \
@@ -482,26 +483,28 @@ void __ast_string_field_release_active(struct ast_string_field_pool *pool_head,
 
 #define __ast_string_field_ptr_set_by_fields(field_mgr_pool, field_mgr, ptr, data, file, lineno, func) \
 ({                                                                                             \
-    int __res__ = 0;                                                                           \
-    const char *__d__ = (data);                                                                \
-    size_t __dlen__ = (__d__) ? strlen(__d__) + 1 : 1;                                         \
-    ast_string_field *__p__ = (ast_string_field *) (ptr);                                      \
-    ast_string_field target = *__p__;                                                          \
-    if (__dlen__ == 1) {                                                                       \
-        __ast_string_field_release_active(field_mgr_pool, *__p__);                             \
-        *__p__ = __ast_string_field_empty;                                                     \
-    } else if ((__dlen__ <= AST_STRING_FIELD_ALLOCATION(*__p__)) ||                            \
-           (!__ast_string_field_ptr_grow(&field_mgr, &field_mgr_pool, __dlen__, __p__)) ||     \
-           (target = __ast_string_field_alloc_space(&field_mgr, &field_mgr_pool, __dlen__, file, lineno, func))) { \
-        if (target != *__p__) {                                                                \
-            __ast_string_field_release_active(field_mgr_pool, *__p__);                         \
-            *__p__ = target;                                                                   \
-        }                                                                                      \
-        memcpy(* (void **) __p__, __d__, __dlen__);                                            \
-    } else {                                                                                   \
-        __res__ = -1;                                                                          \
-    }                                                                                          \
-    __res__;                                                                                   \
+	int __res__ = 0;                                                                           \
+	const char *__d__ = (data);                                                                \
+	ast_string_field *__p__ = (ast_string_field *) (ptr);                                      \
+	ast_string_field target = *__p__;                                                          \
+	if (__d__ == NULL || *__d__ == '\0') {                                                     \
+		__ast_string_field_release_active(field_mgr_pool, *__p__);                             \
+		*__p__ = __ast_string_field_empty;                                                     \
+	} else {                                                                                   \
+		size_t __dlen__ = strlen(__d__) + 1;                                                   \
+		if ((__dlen__ <= AST_STRING_FIELD_ALLOCATION(*__p__)) ||                               \
+			(!__ast_string_field_ptr_grow(&field_mgr, &field_mgr_pool, __dlen__, __p__)) ||    \
+			(target = __ast_string_field_alloc_space(&field_mgr, &field_mgr_pool, __dlen__, file, lineno, func))) { \
+			if (target != *__p__) {                                                            \
+				__ast_string_field_release_active(field_mgr_pool, *__p__);                     \
+				*__p__ = target;                                                               \
+			}                                                                                  \
+			memcpy(* (void **) __p__, __d__, __dlen__);                                        \
+		} else {                                                                               \
+			__res__ = -1;                                                                      \
+		}                                                                                      \
+	}                                                                                          \
+	__res__;                                                                                   \
 })
 
 #define ast_string_field_ptr_set_by_fields(field_mgr_pool, field_mgr, ptr, data) \
@@ -518,7 +521,7 @@ void __ast_string_field_release_active(struct ast_string_field_pool *pool_head,
 #define ast_string_field_set(x, field, data) \
 ({ \
 	int __res__ = -1; \
-	if (((void *)(x)) != NULL) { \
+	if (((void *)(x)) != (void *)NULL) { \
 		__res__ = ast_string_field_ptr_set(x, &(x)->field, data); \
 	} \
 	__res__; \
@@ -534,7 +537,7 @@ void __ast_string_field_release_active(struct ast_string_field_pool *pool_head,
 #define ast_string_field_ptr_build(x, ptr, fmt, args...) \
 ({ \
 	int __res__ = -1; \
-	if (((void *)(x)) != NULL) { \
+	if (((void *)(x)) != (void *)NULL) { \
 		__ast_string_field_ptr_build(__FILE__, __LINE__, __PRETTY_FUNCTION__, \
 			&(x)->__field_mgr, &(x)->__field_mgr_pool, (ast_string_field *) ptr, fmt, args); \
 		__res__ = 0; \
@@ -552,7 +555,7 @@ void __ast_string_field_release_active(struct ast_string_field_pool *pool_head,
 #define ast_string_field_build(x, field, fmt, args...) \
 ({ \
 	int __res__ = -1; \
-	if (((void *)(x)) != NULL) { \
+	if (((void *)(x)) != (void *)NULL) { \
 		__ast_string_field_ptr_build(__FILE__, __LINE__, __PRETTY_FUNCTION__, \
 			&(x)->__field_mgr, &(x)->__field_mgr_pool, (ast_string_field *) &(x)->field, fmt, args); \
 		__res__ = 0; \
@@ -570,7 +573,7 @@ void __ast_string_field_release_active(struct ast_string_field_pool *pool_head,
 #define ast_string_field_ptr_build_va(x, ptr, fmt, args) \
 ({ \
 	int __res__ = -1; \
-	if (((void *)(x)) != NULL) { \
+	if (((void *)(x)) != (void *)NULL) { \
 		__ast_string_field_ptr_build_va(&(x)->__field_mgr, &(x)->__field_mgr_pool, (ast_string_field *) ptr, fmt, args, \
 			__FILE__, __LINE__, __PRETTY_FUNCTION__); \
 		__res__ = 0; \
@@ -588,7 +591,7 @@ void __ast_string_field_release_active(struct ast_string_field_pool *pool_head,
 #define ast_string_field_build_va(x, field, fmt, args) \
 ({ \
 	int __res__ = -1; \
-	if (((void *)(x)) != NULL) { \
+	if (((void *)(x)) != (void *)NULL) { \
 		__ast_string_field_ptr_build_va(&(x)->__field_mgr, &(x)->__field_mgr_pool, (ast_string_field *) &(x)->field, fmt, args, \
 			__FILE__, __LINE__, __PRETTY_FUNCTION__); \
 		__res__ = 0; \
@@ -607,7 +610,7 @@ void __ast_string_field_release_active(struct ast_string_field_pool *pool_head,
 #define ast_string_fields_cmp(instance1, instance2) \
 ({ \
 	int __res__ = -1; \
-	if (((void *)(instance1)) != NULL && ((void *)(instance2)) != NULL) { \
+	if (((void *)(instance1)) != (void *)NULL && ((void *)(instance2)) != (void *)NULL) { \
 		__res__ = __ast_string_fields_cmp(&(instance1)->__field_mgr.string_fields, \
 			&(instance2)->__field_mgr.string_fields); \
 	} \
@@ -627,7 +630,7 @@ int __ast_string_fields_cmp(struct ast_string_field_vector *left, struct ast_str
 #define ast_string_fields_copy(copy, orig) \
 ({ \
 	int __res__ = -1; \
-	if (((void *)(copy)) != NULL && ((void *)(orig)) != NULL) { \
+	if (((void *)(copy)) != (void *)NULL && ((void *)(orig)) != (void *)NULL) { \
 		__res__ = __ast_string_fields_copy(((copy)->__field_mgr_pool), \
 			(struct ast_string_field_mgr *)&((copy)->__field_mgr), \
 			(struct ast_string_field_mgr *)&((orig)->__field_mgr), \
diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h
index ab46273b60f86ce7caac3404d98a6c7baf15d452..f8ad4140d8fea4d3eca0c5a08f68f05100514c5d 100644
--- a/include/asterisk/strings.h
+++ b/include/asterisk/strings.h
@@ -265,6 +265,7 @@ enum ast_strsep_flags {
   \param sep A single character delimiter.
   \param flags Controls post-processing of the result.
   AST_STRSEP_TRIM trims all leading and trailing whitespace from the result.
+  If the result containes only whitespace, it'll be passed through unchanged.
   AST_STRSEP_STRIP does a trim then strips the outermost quotes.  You may want
   to trim again after the strip.  Just OR both the TRIM and STRIP flags.
   AST_STRSEP_UNESCAPE unescapes '\' sequences.
@@ -308,6 +309,24 @@ enum ast_strsep_flags {
  */
 char *ast_strsep(char **s, const char sep, uint32_t flags);
 
+/*!
+ * \brief Like ast_strsep() except you can specify a specific quote character
+ *
+  \param s Pointer to address of the string to be processed.
+  Will be modified and can't be constant.
+  \param sep A single character delimiter.
+  \param quote The quote character
+  \param flags Controls post-processing of the result.
+  AST_STRSEP_TRIM trims all leading and trailing whitespace from the result.
+  AST_STRSEP_STRIP does a trim then strips the outermost quotes.  You may want
+  to trim again after the strip.  Just OR both the TRIM and STRIP flags.
+  AST_STRSEP_UNESCAPE unescapes '\' sequences.
+  AST_STRSEP_ALL does all of the above processing.
+  \return The next token or NULL if done or if there are more than 8 levels of
+  nested quotes.
+ */
+char *ast_strsep_quoted(char **s, const char sep, const char quote, uint32_t flags);
+
 /*!
   \brief Strip backslash for "escaped" semicolons,
 	the string to be stripped (will be modified).
@@ -393,11 +412,13 @@ char *ast_escape_c_alloc(const char *s);
 AST_INLINE_API(
 void ast_copy_string(char *dst, const char *src, size_t size),
 {
-	while (*src && size) {
-		*dst++ = *src++;
-		size--;
+	volatile size_t sz = size;
+	volatile char *sp = (char *)src;
+	while (*sp && sz) {
+		*dst++ = *sp++;
+		sz--;
 	}
-	if (__builtin_expect(!size, 0))
+	if (__builtin_expect(!sz, 0))
 		dst--;
 	*dst = '\0';
 }
@@ -1350,7 +1371,6 @@ static force_inline char *ast_str_to_upper(char *str)
  * \return AO2 container for strings
  * \retval NULL if allocation failed
  */
-//struct ao2_container *ast_str_container_alloc_options(enum ao2_container_opts opts, int buckets);
 struct ao2_container *ast_str_container_alloc_options(enum ao2_alloc_opts opts, int buckets);
 
 /*!
diff --git a/include/asterisk/test.h b/include/asterisk/test.h
index e23aca8df55288e519c5eb4982ca5784e7fdb9a8..d2be92c6fd1a90b3bd6e17f103124ae86bba4590 100644
--- a/include/asterisk/test.h
+++ b/include/asterisk/test.h
@@ -108,7 +108,7 @@
 \code
    'test show registered all'  will show every registered test.
    'test execute all'          will execute every registered test.
-   'test show results all'     will show detailed results for ever executed test
+   'test show results all'     will show detailed results for every executed test
    'test generate results xml' will generate a test report in xml format
    'test generate results txt' will generate a test report in txt format
 \endcode
@@ -208,6 +208,27 @@ enum ast_test_command {
  */
 struct ast_test;
 
+/*!
+ * \brief A capture of running an external process.
+ *
+ * This contains a buffer holding stdout, another containing stderr,
+ * the process id of the child, and its exit code.
+ */
+struct ast_test_capture {
+	/*! \brief buffer holding stdout */
+	char *outbuf;
+	/*! \brief length of buffer holding stdout */
+	size_t outlen;
+	/*! \brief buffer holding stderr */
+	char *errbuf;
+	/*! \brief length of buffer holding stderr */
+	size_t errlen;
+	/*! \brief process id of child */
+	pid_t pid;
+	/*! \brief exit code of child */
+	int exitcode;
+};
+
 /*!
  * \brief Contains all the initialization information required to store a new test definition
  */
@@ -417,5 +438,50 @@ int __ast_test_status_update(const char *file, const char *func, int line, struc
 	} \
 })
 
+/*!
+ * \brief Initialize the capture structure.
+ *
+ * \since 16.30.0, 18.16.0, 19.8.0, 20.1.0
+ *
+ * \param capture The structure describing the child process and its
+ * associated output.
+ */
+void ast_test_capture_init(struct ast_test_capture *capture);
+
+/*!
+ * \brief Release the storage (buffers) associated with capturing
+ * the output of an external child process.
+ *
+ * \since 19.4.0
+ *
+ * \param capture The structure describing the child process and its
+ * associated output.
+ */
+void ast_test_capture_free(struct ast_test_capture *capture);
+
+/*!
+ * \brief Run a child process and capture its output and exit code.
+ *
+ * \!since 19.4.0
+ *
+ * \param capture The structure describing the child process and its
+ * associated output.
+ *
+ * \param file The name of the file to execute (uses $PATH to locate).
+ *
+ * \param argv The NULL-terminated array of arguments to pass to the
+ * child process, starting with the command name itself.
+ *
+ * \param data The buffer of input to be sent to child process's stdin;
+ * optional and may be NULL.
+ *
+ * \param datalen The length of the buffer, if not NULL, otherwise zero.
+ *
+ * \retval 1 for success
+ * \retval other failure
+ */
+
+int ast_test_capture_command(struct ast_test_capture *capture, const char *file, char *const argv[], const char *data, unsigned datalen);
+
 #endif /* TEST_FRAMEWORK */
 #endif /* _AST_TEST_H */
diff --git a/include/asterisk/time.h b/include/asterisk/time.h
index e2ab513384fd2e307066c3991a835340a8dae2e8..130084c167df9d07db2a2d7afbc1736d40beb0ff 100644
--- a/include/asterisk/time.h
+++ b/include/asterisk/time.h
@@ -33,8 +33,17 @@
 #include <unistd.h>
 #endif
 
+#include <math.h>
+
 #include "asterisk/inline_api.h"
 
+/* A time_t can be represented as an unsigned long long (or uint64_t).
+ * Formatted in base 10, UINT64_MAX is 20 digits long, plus one for NUL.
+ * This should be the size of the receiving char buffer for calls to
+ * ast_time_t_to_string().
+ */
+#define AST_TIME_T_LEN		21
+
 /* We have to let the compiler learn what types to use for the elements of a
    struct timeval since on linux, it's time_t and suseconds_t, but on *BSD,
    they are just a long.
@@ -225,6 +234,41 @@ struct timeval ast_tv(ast_time_t sec, ast_suseconds_t usec),
 }
 )
 
+/*!
+ * \brief Returns a timeval structure corresponding to the
+ * number of seconds in the double _td.
+ *
+ * \param _td The number of seconds.
+ * \returns A timeval structure containing the number of seconds.
+ *
+ * This is the inverse of ast_tv2double().
+ */
+AST_INLINE_API(
+struct timeval ast_double2tv(double _td),
+{
+	struct timeval t;
+	t.tv_sec = (typeof(t.tv_sec))floor(_td);
+	t.tv_usec = (typeof(t.tv_usec)) ((_td - t.tv_sec) * 1000000.0);
+	return t;
+}
+)
+
+/*!
+ * \brief Returns a double corresponding to the number of seconds
+ * in the timeval \c tv.
+ *
+ * \param tv A pointer to a timeval structure.
+ * \returns A double containing the number of seconds.
+ *
+ * This is the inverse of ast_double2tv().
+ */
+AST_INLINE_API(
+double ast_tv2double(const struct timeval *tv),
+{
+	return (((double)tv->tv_sec) + (((double)tv->tv_usec) / 1000000.0));
+}
+)
+
 /*!
  * \brief Returns a timeval corresponding to the duration of n samples at rate r.
  * Useful to convert samples to timevals, or even milliseconds to timevals
@@ -237,6 +281,57 @@ struct timeval ast_samp2tv(unsigned int _nsamp, unsigned int _rate),
 }
 )
 
+/*!
+ * \brief Returns the number of samples at rate _rate in the
+ * duration specified by _tv.
+ *
+ * \param _tv A pointer to a timeval structure.
+ * \param _rate The sample rate in Hz.
+ * \returns A time_t containing the number of samples.
+ *
+ * This is the inverse of ast_samp2tv().
+ */
+AST_INLINE_API(
+time_t ast_tv2samp(const struct timeval *_tv, int _rate),
+{
+	return (time_t)(ast_tv2double(_tv) * (double)_rate);
+}
+)
+
+/*!
+ * \brief Returns the duration in seconds of _nsamp samples
+ * at rate _rate.
+ *
+ * \param _nsamp The number of samples
+ * \param _rate The sample rate in Hz.
+ * \returns A double containing the number of seconds.
+ *
+ * This is the inverse of ast_sec2samp().
+ */
+AST_INLINE_API(
+double ast_samp2sec(unsigned int _nsamp, unsigned int _rate),
+{
+	return ((double)_nsamp) / ((double)_rate);
+}
+)
+
+/*!
+ * \brief Returns the number of samples at _rate in the duration
+ * in _seconds.
+ *
+ * \param _seconds The time interval in seconds.
+ * \param _rate The sample rate in Hz.
+ * \returns The number of samples.
+ *
+ * This is the inverse of ast_samp2sec().
+ */
+AST_INLINE_API(
+unsigned int ast_sec2samp(double _seconds, int _rate),
+{
+	return (unsigned int)(_seconds * _rate);
+}
+)
+
 /*!
  * \brief Time units enumeration.
  */
@@ -316,4 +411,17 @@ struct timeval ast_time_create_by_unit(unsigned long val, enum TIME_UNIT unit);
  */
 struct timeval ast_time_create_by_unit_str(unsigned long val, const char *unit);
 
+/*!
+ * \brief Converts to a string representation of a time_t as decimal
+ * seconds since the epoch. Returns -1 on failure, zero otherwise.
+ *
+ * The buffer should be at least 22 bytes long.
+ */
+int ast_time_t_to_string(time_t time, char *buf, size_t length);
+
+/*!
+ * \brief Returns a time_t from a string containing seconds since the epoch.
+ */
+time_t ast_string_to_time_t(const char *str);
+
 #endif /* _ASTERISK_TIME_H */
diff --git a/include/asterisk/utf8.h b/include/asterisk/utf8.h
index 02ec800a243c3ef2bc18de9b015fef139dbb0448..6e6a89dd86dbc7e456fe74eaa34a621ad80edfe9 100644
--- a/include/asterisk/utf8.h
+++ b/include/asterisk/utf8.h
@@ -67,6 +67,59 @@ int ast_utf8_is_validn(const char *str, size_t size);
  */
 void ast_utf8_copy_string(char *dst, const char *src, size_t size);
 
+enum ast_utf8_replace_result {
+	/*! \brief Source contained fully valid UTF-8
+	 *
+	 * The entire string was valid UTF-8 and no replacement
+	 * was required.
+	 */
+	AST_UTF8_REPLACE_VALID,
+
+	/*! \brief Source contained at least 1 invalid UTF-8 sequence
+	 *
+	 * Parts of the string contained invalid UTF-8 sequences
+	 * but those were successfully replaced with the U+FFFD
+	 * replacement sequence.
+	 */
+	AST_UTF8_REPLACE_INVALID,
+
+	/*! \brief Not enough space to copy entire source
+	 *
+	 * The destination buffer wasn't large enough to copy
+	 * all of the source characters.  As many of the source
+	 * characters that could be copied/replaced were done so
+	 * and a final NULL terminator added.
+	 */
+	AST_UTF8_REPLACE_OVERRUN,
+};
+
+/*!
+ * \brief Copy a string safely replacing any invalid UTF-8 sequences
+ *
+ * This is similar to \ref ast_copy_string, but it will only copy valid UTF-8
+ * sequences from the source string into the destination buffer.
+ * If an invalid sequence is encountered, it's replaced with the \uFFFD
+ * sequence which is the valid UTF-8 sequence that represents an unknown,
+ * unrecognized, or unrepresentable character.  Since \uFFFD is actually a
+ * 3 byte sequence, the destination buffer will need to be larger than
+ * the corresponding source string if it contains invalid sequences.
+ * You can pass NULL as the destination buffer pointer to get the actual
+ * size required, then call the function again with the properly sized
+ * buffer.
+ *
+ * \param dst       Pointer to the destination buffer. If NULL,
+ *                  dst_size will be set to the size of the
+ *                  buffer required to fully process the
+ *                  source string.
+ * \param dst_size  A pointer to the size of the dst buffer
+ * \param src       The source string
+ * \param src_len   The number of bytes to copy
+ *
+ * \return \ref ast_utf8_replace_result
+ */
+enum ast_utf8_replace_result ast_utf8_replace_invalid_chars(char *dst,
+	size_t *dst_size, const char *src, size_t src_len);
+
 enum ast_utf8_validation_result {
 	/*! \brief The consumed sequence is valid UTF-8
 	 *
diff --git a/include/asterisk/utils.h b/include/asterisk/utils.h
index 72deaa3952ecb7639cb90041a00451b08ec8ac57..3c06e834eba2ea440d95a9a17e7d0d7708d138aa 100644
--- a/include/asterisk/utils.h
+++ b/include/asterisk/utils.h
@@ -1105,4 +1105,14 @@ int ast_thread_user_interface_set(int is_user_interface);
  */
 int ast_thread_is_user_interface(void);
 
+/*!
+ * \brief Test for the presence of an executable command in $PATH
+ *
+ * \param cmd Name of command to locate.
+ *
+ * \retval True (non-zero) if command is in $PATH.
+ * \retval False (zero) command not found.
+ */
+int ast_check_command_in_path(const char *cmd);
+
 #endif /* _ASTERISK_UTILS_H */
diff --git a/include/asterisk/xml.h b/include/asterisk/xml.h
index 9d43560a3e40fc0155188f35352d6a229c23a6da..1350444d6450846732beb9c3d49bca584f0b41d1 100644
--- a/include/asterisk/xml.h
+++ b/include/asterisk/xml.h
@@ -163,6 +163,7 @@ void ast_xml_free_text(const char *text);
  * \param attrname Attribute name.
  * \retval NULL on error
  * \return The attribute value on success.
+ * \note The result must be freed with ast_xml_free_attr().
  */
 const char *ast_xml_get_attribute(struct ast_xml_node *node, const char *attrname);
 
@@ -188,6 +189,18 @@ int ast_xml_set_attribute(struct ast_xml_node *node, const char *name, const cha
 struct ast_xml_node *ast_xml_find_element(struct ast_xml_node *root_node, const char *name, const char *attrname, const char *attrvalue);
 struct ast_xml_ns *ast_xml_find_namespace(struct ast_xml_doc *doc, struct ast_xml_node *node, const char *ns_name);
 
+/*!
+ * \brief Find a direct child element by name.
+ * \param _parent_node This is the parent node to search.
+ * \param _name Node name to find.
+ * \param _attrname attribute name to match (if NULL it won't be matched).
+ * \param _attrvalue attribute value to match (if NULL it won't be matched).
+ * \retval NULL if not found.
+ * \return The node on success.
+ */
+#define ast_xml_find_child_element(_parent_node, _name, _attrname, _attrvalue) \
+    ast_xml_find_element(ast_xml_node_get_children(_parent_node), _name, _attrname, _attrvalue)
+
 /*!
  * \brief Get the prefix of a namespace.
  * \param ns The namespace
@@ -248,6 +261,17 @@ struct ast_xml_node *ast_xml_node_get_parent(struct ast_xml_node *node);
  * \brief Dump the specified document to a file. */
 int ast_xml_doc_dump_file(FILE *output, struct ast_xml_doc *doc);
 
+/*!
+ * \brief Dump the specified document to a buffer
+ *
+ * \param doc The XML doc to dump
+ * \param buffer A pointer to a char * to receive the address of the results
+ * \param length A pointer to an int to receive the length of the results
+ *
+ * \note The result buffer must be freed with ast_xml_free_text().
+ */
+void ast_xml_doc_dump_memory(struct ast_xml_doc *doc, char **buffer, int *length);
+
 /*!
  * \brief Free the XPath results
  * \param results The XPath results object to dispose of
@@ -347,7 +371,7 @@ void ast_xslt_close(struct ast_xslt_doc *xslt);
  * \brief Apply an XSLT stylesheet to an XML document
  *
  * \param xslt    XSLT stylesheet to apply.
- * \param xml     XML document the stylesheet will be applied to.
+ * \param doc     XML document the stylesheet will be applied to.
  * \param params  An array of name value pairs to pass as parameters
  *                The array must terminate with a NULL sentinel.
  *                Example:  { "name1", "value1", "name2", "value2", NULL }
@@ -359,11 +383,11 @@ struct ast_xml_doc *ast_xslt_apply(struct ast_xslt_doc *xslt, struct ast_xml_doc
 /*!
  * \brief Save the results of applying a stylesheet to a string
  *
- * \param buffer[out]  A pointer to a char * to receive the address of the result string.
+ * \param[out] buffer  A pointer to a char * to receive the address of the result string.
  *                     The buffer must be freed with ast_xml_free_text().
- * \param length[out]  A pointer to an int to receive the result string length.
- * \param result       The result document from ast_xslt_apply.
- * \param xslt         The stylesheet that was applied.
+ * \param[out] length  A pointer to an int to receive the result string length.
+ * \param      result  The result document from ast_xslt_apply.
+ * \param      xslt    The stylesheet that was applied.
  *
  * \return 0 on success, any other value on failure.
  */
diff --git a/main/Makefile b/main/Makefile
index 299998c0493f46554e432740caa2eb2d3f7e55f8..297391746e91d472cf30fa40a336a35b2c43cb5d 100644
--- a/main/Makefile
+++ b/main/Makefile
@@ -133,13 +133,10 @@ else
 ast_expr2f.c:
 endif
 	$(ECHO_PREFIX) echo "   [FLEX] $< -> $@"
-	$(CMD_PREFIX) $(FLEX) -o $@ ast_expr2.fl
-	$(CMD_PREFIX) sed 's@#if __STDC_VERSION__ >= 199901L@#if !defined __STDC_VERSION__ || __STDC_VERSION__ >= 199901L@' $@ > $@.fix
 	$(CMD_PREFIX) echo "#define ASTMM_LIBC ASTMM_REDIRECT" > $@
 	$(CMD_PREFIX) echo "#include \"asterisk.h\"" >> $@
 	$(CMD_PREFIX) echo >> $@
-	$(CMD_PREFIX) cat $@.fix >> $@
-	$(CMD_PREFIX) rm $@.fix
+	$(CMD_PREFIX) $(FLEX) -t ast_expr2.fl >> $@
 
 ifneq ($(findstring ENABLE_UPLOADS,$(MENUSELECT_CFLAGS)),)
 GMIMELDFLAGS+=$(GMIME_LIB)
@@ -167,8 +164,12 @@ lock.o: _ASTCFLAGS+=$(call get_menuselect_cflags,DETECT_DEADLOCKS)
 options.o: _ASTCFLAGS+=$(call get_menuselect_cflags,REF_DEBUG)
 sched.o: _ASTCFLAGS+=$(call get_menuselect_cflags,DEBUG_SCHEDULER DUMP_SCHEDULER)
 tcptls.o: _ASTCFLAGS+=$(OPENSSL_INCLUDE) -Wno-deprecated-declarations
+# since we're using open_memstream(), we need to release the buffer with
+# the native free() function or we might get unexpected behavior.
+test.o: _ASTCFLAGS+=-DASTMM_LIBC=ASTMM_IGNORE
 uuid.o: _ASTCFLAGS+=$(UUID_INCLUDE)
 stasis.o: _ASTCFLAGS+=$(call get_menuselect_cflags,AO2_DEBUG)
+time.o: _ASTCFLAGS+=-D_XOPEN_SOURCE=700
 
 
 OBJS:=$(sort $(OBJS))
diff --git a/main/ast_expr2.c b/main/ast_expr2.c
index 5b8f6e8b780cf3a9b2dfe3fc4d295c750def5731..ac02037319d5ad7681e971eb51a6e01dc4bc82c5 100644
--- a/main/ast_expr2.c
+++ b/main/ast_expr2.c
@@ -2,20 +2,20 @@
 /* A Bison parser, made by GNU Bison 2.4.1.  */
 
 /* Skeleton implementation for Bison's Yacc-like parsers in C
-
+   
       Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
    Free Software Foundation, Inc.
-
+   
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
-
+   
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
-
+   
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
@@ -28,7 +28,7 @@
    special exception, which will cause the skeleton and the resulting
    Bison output files to be licensed under the GNU General Public
    License without this special exception.
-
+   
    This special exception was added by the Free Software Foundation in
    version 2.2 of Bison.  */
 
@@ -413,7 +413,7 @@ int		ast_yyerror(const char *,YYLTYPE *, struct parse_io *);
 
 
 /* Line 189 of yacc.c  */
-#line 419 "ast_expr2.c"
+#line 417 "ast_expr2.c"
 
 /* Enabling traces.  */
 #ifndef YYDEBUG
@@ -473,7 +473,7 @@ typedef union YYSTYPE
 {
 
 /* Line 214 of yacc.c  */
-#line 345 "ast_expr2.y"
+#line 343 "ast_expr2.y"
 
 	struct val *val;
 	struct expr_node *arglist;
@@ -481,7 +481,7 @@ typedef union YYSTYPE
 
 
 /* Line 214 of yacc.c  */
-#line 487 "ast_expr2.c"
+#line 485 "ast_expr2.c"
 } YYSTYPE;
 # define YYSTYPE_IS_TRIVIAL 1
 # define yystype YYSTYPE /* obsolescent; will be withdrawn */
@@ -505,13 +505,13 @@ typedef struct YYLTYPE
 /* Copy the second part of user declarations.  */
 
 /* Line 264 of yacc.c  */
-#line 350 "ast_expr2.y"
+#line 348 "ast_expr2.y"
 
 extern int		ast_yylex __P((YYSTYPE *, YYLTYPE *, yyscan_t));
 
 
 /* Line 264 of yacc.c  */
-#line 517 "ast_expr2.c"
+#line 515 "ast_expr2.c"
 
 #ifdef short
 # undef short
@@ -809,9 +809,9 @@ static const yytype_int8 yyrhs[] =
 /* YYRLINE[YYN] -- source line where rule number YYN was defined.  */
 static const yytype_uint16 yyrline[] =
 {
-       0,   374,   374,   382,   389,   390,   396,   405,   411,   412,
-     416,   420,   424,   428,   432,   436,   440,   444,   448,   452,
-     456,   460,   464,   468,   472,   476,   480,   484,   489
+       0,   372,   372,   380,   387,   388,   394,   403,   409,   410,
+     414,   418,   422,   426,   430,   434,   438,   442,   446,   450,
+     454,   458,   462,   466,   470,   474,   478,   482,   487
 };
 #endif
 
@@ -1226,7 +1226,7 @@ int yydebug;
 # define YYMAXDEPTH 10000
 #endif
 
-
+
 
 #if YYERROR_VERBOSE
 
@@ -1437,7 +1437,7 @@ yysyntax_error (char *yyresult, int yystate, int yychar)
     }
 }
 #endif /* YYERROR_VERBOSE */
-
+
 
 /*-----------------------------------------------.
 | Release the memory associated to this symbol.  |
@@ -1469,209 +1469,209 @@ yydestruct (yymsg, yytype, yyvaluep, yylocationp)
       case 4: /* "TOK_COLONCOLON" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1479 "ast_expr2.c"
+#line 1477 "ast_expr2.c"
 	break;
       case 5: /* "TOK_COND" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1488 "ast_expr2.c"
+#line 1486 "ast_expr2.c"
 	break;
       case 6: /* "TOK_OR" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1497 "ast_expr2.c"
+#line 1495 "ast_expr2.c"
 	break;
       case 7: /* "TOK_AND" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1506 "ast_expr2.c"
+#line 1504 "ast_expr2.c"
 	break;
       case 8: /* "TOK_NE" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1515 "ast_expr2.c"
+#line 1513 "ast_expr2.c"
 	break;
       case 9: /* "TOK_LE" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1524 "ast_expr2.c"
+#line 1522 "ast_expr2.c"
 	break;
       case 10: /* "TOK_GE" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1533 "ast_expr2.c"
+#line 1531 "ast_expr2.c"
 	break;
       case 11: /* "TOK_LT" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1542 "ast_expr2.c"
+#line 1540 "ast_expr2.c"
 	break;
       case 12: /* "TOK_GT" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1551 "ast_expr2.c"
+#line 1549 "ast_expr2.c"
 	break;
       case 13: /* "TOK_EQ" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1560 "ast_expr2.c"
+#line 1558 "ast_expr2.c"
 	break;
       case 14: /* "TOK_MINUS" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1569 "ast_expr2.c"
+#line 1567 "ast_expr2.c"
 	break;
       case 15: /* "TOK_PLUS" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1578 "ast_expr2.c"
+#line 1576 "ast_expr2.c"
 	break;
       case 16: /* "TOK_MOD" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1587 "ast_expr2.c"
+#line 1585 "ast_expr2.c"
 	break;
       case 17: /* "TOK_DIV" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1596 "ast_expr2.c"
+#line 1594 "ast_expr2.c"
 	break;
       case 18: /* "TOK_MULT" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1605 "ast_expr2.c"
+#line 1603 "ast_expr2.c"
 	break;
       case 19: /* "TOK_COMPL" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1614 "ast_expr2.c"
+#line 1612 "ast_expr2.c"
 	break;
       case 20: /* "TOK_TILDETILDE" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1623 "ast_expr2.c"
+#line 1621 "ast_expr2.c"
 	break;
       case 21: /* "TOK_EQTILDE" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1632 "ast_expr2.c"
+#line 1630 "ast_expr2.c"
 	break;
       case 22: /* "TOK_COLON" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1641 "ast_expr2.c"
+#line 1639 "ast_expr2.c"
 	break;
       case 23: /* "TOK_LP" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1650 "ast_expr2.c"
+#line 1648 "ast_expr2.c"
 	break;
       case 24: /* "TOK_RP" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1659 "ast_expr2.c"
+#line 1657 "ast_expr2.c"
 	break;
       case 25: /* "TOKEN" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1668 "ast_expr2.c"
+#line 1666 "ast_expr2.c"
 	break;
       case 29: /* "expr" */
 
 /* Line 1000 of yacc.c  */
-#line 368 "ast_expr2.y"
+#line 366 "ast_expr2.y"
 	{  free_value((yyvaluep->val)); };
 
 /* Line 1000 of yacc.c  */
-#line 1677 "ast_expr2.c"
+#line 1675 "ast_expr2.c"
 	break;
 
       default:
@@ -2002,7 +2002,7 @@ yyreduce:
         case 2:
 
 /* Line 1455 of yacc.c  */
-#line 374 "ast_expr2.y"
+#line 372 "ast_expr2.y"
     { ((struct parse_io *)parseio)->val = (struct val *)calloc(sizeof(struct val),1);
               ((struct parse_io *)parseio)->val->type = (yyvsp[(1) - (1)].val)->type;
               if( (yyvsp[(1) - (1)].val)->type == AST_EXPR_number )
@@ -2016,7 +2016,7 @@ yyreduce:
   case 3:
 
 /* Line 1455 of yacc.c  */
-#line 382 "ast_expr2.y"
+#line 380 "ast_expr2.y"
     {/* nothing */ ((struct parse_io *)parseio)->val = (struct val *)calloc(sizeof(struct val),1);
               ((struct parse_io *)parseio)->val->type = AST_EXPR_string;
 			  ((struct parse_io *)parseio)->val->u.s = strdup("");
@@ -2026,14 +2026,14 @@ yyreduce:
   case 4:
 
 /* Line 1455 of yacc.c  */
-#line 389 "ast_expr2.y"
+#line 387 "ast_expr2.y"
     { (yyval.arglist) = alloc_expr_node(AST_EXPR_NODE_VAL); (yyval.arglist)->val = (yyvsp[(1) - (1)].val);;}
     break;
 
   case 5:
 
 /* Line 1455 of yacc.c  */
-#line 390 "ast_expr2.y"
+#line 388 "ast_expr2.y"
     {struct expr_node *x = alloc_expr_node(AST_EXPR_NODE_VAL);
                                  struct expr_node *t;
 								 DESTROY((yyvsp[(2) - (3)].val));
@@ -2045,7 +2045,7 @@ yyreduce:
   case 6:
 
 /* Line 1455 of yacc.c  */
-#line 396 "ast_expr2.y"
+#line 394 "ast_expr2.y"
     {struct expr_node *x = alloc_expr_node(AST_EXPR_NODE_VAL);
                                  struct expr_node *t;  /* NULL args should OK */
 								 DESTROY((yyvsp[(2) - (2)].val));
@@ -2057,7 +2057,7 @@ yyreduce:
   case 7:
 
 /* Line 1455 of yacc.c  */
-#line 405 "ast_expr2.y"
+#line 403 "ast_expr2.y"
     { (yyval.val) = op_func((yyvsp[(1) - (4)].val),(yyvsp[(3) - (4)].arglist), ((struct parse_io *)parseio)->chan);
 		                            DESTROY((yyvsp[(2) - (4)].val));
 									DESTROY((yyvsp[(4) - (4)].val));
@@ -2069,14 +2069,14 @@ yyreduce:
   case 8:
 
 /* Line 1455 of yacc.c  */
-#line 411 "ast_expr2.y"
+#line 409 "ast_expr2.y"
     {(yyval.val) = (yyvsp[(1) - (1)].val);;}
     break;
 
   case 9:
 
 /* Line 1455 of yacc.c  */
-#line 412 "ast_expr2.y"
+#line 410 "ast_expr2.y"
     { (yyval.val) = (yyvsp[(2) - (3)].val);
 	                       (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
 						   (yyloc).first_line=0; (yyloc).last_line=0;
@@ -2086,7 +2086,7 @@ yyreduce:
   case 10:
 
 /* Line 1455 of yacc.c  */
-#line 416 "ast_expr2.y"
+#line 414 "ast_expr2.y"
     { (yyval.val) = op_or ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
                          (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2096,7 +2096,7 @@ yyreduce:
   case 11:
 
 /* Line 1455 of yacc.c  */
-#line 420 "ast_expr2.y"
+#line 418 "ast_expr2.y"
     { (yyval.val) = op_and ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                      (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2106,7 +2106,7 @@ yyreduce:
   case 12:
 
 /* Line 1455 of yacc.c  */
-#line 424 "ast_expr2.y"
+#line 422 "ast_expr2.y"
     { (yyval.val) = op_eq ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                     (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2116,7 +2116,7 @@ yyreduce:
   case 13:
 
 /* Line 1455 of yacc.c  */
-#line 428 "ast_expr2.y"
+#line 426 "ast_expr2.y"
     { (yyval.val) = op_gt ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
                          (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2126,7 +2126,7 @@ yyreduce:
   case 14:
 
 /* Line 1455 of yacc.c  */
-#line 432 "ast_expr2.y"
+#line 430 "ast_expr2.y"
     { (yyval.val) = op_lt ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                     (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2136,7 +2136,7 @@ yyreduce:
   case 15:
 
 /* Line 1455 of yacc.c  */
-#line 436 "ast_expr2.y"
+#line 434 "ast_expr2.y"
     { (yyval.val) = op_ge ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                      (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2146,7 +2146,7 @@ yyreduce:
   case 16:
 
 /* Line 1455 of yacc.c  */
-#line 440 "ast_expr2.y"
+#line 438 "ast_expr2.y"
     { (yyval.val) = op_le ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                      (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2156,7 +2156,7 @@ yyreduce:
   case 17:
 
 /* Line 1455 of yacc.c  */
-#line 444 "ast_expr2.y"
+#line 442 "ast_expr2.y"
     { (yyval.val) = op_ne ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                      (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2166,7 +2166,7 @@ yyreduce:
   case 18:
 
 /* Line 1455 of yacc.c  */
-#line 448 "ast_expr2.y"
+#line 446 "ast_expr2.y"
     { (yyval.val) = op_plus ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                       (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2176,7 +2176,7 @@ yyreduce:
   case 19:
 
 /* Line 1455 of yacc.c  */
-#line 452 "ast_expr2.y"
+#line 450 "ast_expr2.y"
     { (yyval.val) = op_minus ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                        (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2186,7 +2186,7 @@ yyreduce:
   case 20:
 
 /* Line 1455 of yacc.c  */
-#line 456 "ast_expr2.y"
+#line 454 "ast_expr2.y"
     { (yyval.val) = op_negate ((yyvsp[(2) - (2)].val));
 						DESTROY((yyvsp[(1) - (2)].val));
 	                        (yyloc).first_column = (yylsp[(1) - (2)]).first_column; (yyloc).last_column = (yylsp[(2) - (2)]).last_column;
@@ -2196,7 +2196,7 @@ yyreduce:
   case 21:
 
 /* Line 1455 of yacc.c  */
-#line 460 "ast_expr2.y"
+#line 458 "ast_expr2.y"
     { (yyval.val) = op_compl ((yyvsp[(2) - (2)].val));
 						DESTROY((yyvsp[(1) - (2)].val));
 	                        (yyloc).first_column = (yylsp[(1) - (2)]).first_column; (yyloc).last_column = (yylsp[(2) - (2)]).last_column;
@@ -2206,7 +2206,7 @@ yyreduce:
   case 22:
 
 /* Line 1455 of yacc.c  */
-#line 464 "ast_expr2.y"
+#line 462 "ast_expr2.y"
     { (yyval.val) = op_times ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                       (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2216,7 +2216,7 @@ yyreduce:
   case 23:
 
 /* Line 1455 of yacc.c  */
-#line 468 "ast_expr2.y"
+#line 466 "ast_expr2.y"
     { (yyval.val) = op_div ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                      (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2226,7 +2226,7 @@ yyreduce:
   case 24:
 
 /* Line 1455 of yacc.c  */
-#line 472 "ast_expr2.y"
+#line 470 "ast_expr2.y"
     { (yyval.val) = op_rem ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                      (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2236,7 +2236,7 @@ yyreduce:
   case 25:
 
 /* Line 1455 of yacc.c  */
-#line 476 "ast_expr2.y"
+#line 474 "ast_expr2.y"
     { (yyval.val) = op_colon ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                        (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2246,7 +2246,7 @@ yyreduce:
   case 26:
 
 /* Line 1455 of yacc.c  */
-#line 480 "ast_expr2.y"
+#line 478 "ast_expr2.y"
     { (yyval.val) = op_eqtilde ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                        (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2256,7 +2256,7 @@ yyreduce:
   case 27:
 
 /* Line 1455 of yacc.c  */
-#line 484 "ast_expr2.y"
+#line 482 "ast_expr2.y"
     { (yyval.val) = op_cond ((yyvsp[(1) - (5)].val), (yyvsp[(3) - (5)].val), (yyvsp[(5) - (5)].val));
 						DESTROY((yyvsp[(2) - (5)].val));
 						DESTROY((yyvsp[(4) - (5)].val));
@@ -2267,7 +2267,7 @@ yyreduce:
   case 28:
 
 /* Line 1455 of yacc.c  */
-#line 489 "ast_expr2.y"
+#line 487 "ast_expr2.y"
     { (yyval.val) = op_tildetilde ((yyvsp[(1) - (3)].val), (yyvsp[(3) - (3)].val));
 						DESTROY((yyvsp[(2) - (3)].val));
 	                        (yyloc).first_column = (yylsp[(1) - (3)]).first_column; (yyloc).last_column = (yylsp[(3) - (3)]).last_column;
@@ -2277,7 +2277,7 @@ yyreduce:
 
 
 /* Line 1455 of yacc.c  */
-#line 2283 "ast_expr2.c"
+#line 2281 "ast_expr2.c"
       default: break;
     }
   YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
@@ -2496,7 +2496,7 @@ yyreturn:
 
 
 /* Line 1675 of yacc.c  */
-#line 495 "ast_expr2.y"
+#line 493 "ast_expr2.y"
 
 
 static struct expr_node *alloc_expr_node(enum node_type nt)
@@ -3685,3 +3685,4 @@ op_tildetilde (struct val *a, struct val *b)
 
 	return v;
 }
+
diff --git a/main/ast_expr2.h b/main/ast_expr2.h
index 628283405796d38592c2065d2fc570c11d7857dd..0b0ae696e0813457ebc62d25b6a4fbe1de15d4c9 100644
--- a/main/ast_expr2.h
+++ b/main/ast_expr2.h
@@ -2,20 +2,20 @@
 /* A Bison parser, made by GNU Bison 2.4.1.  */
 
 /* Skeleton interface for Bison's Yacc-like parsers in C
-
+   
       Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
    Free Software Foundation, Inc.
-
+   
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
-
+   
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
-
+   
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
@@ -28,7 +28,7 @@
    special exception, which will cause the skeleton and the resulting
    Bison output files to be licensed under the GNU General Public
    License without this special exception.
-
+   
    This special exception was added by the Free Software Foundation in
    version 2.2 of Bison.  */
 
@@ -72,7 +72,7 @@ typedef union YYSTYPE
 {
 
 /* Line 1676 of yacc.c  */
-#line 345 "ast_expr2.y"
+#line 343 "ast_expr2.y"
 
 	struct val *val;
 	struct expr_node *arglist;
@@ -101,3 +101,6 @@ typedef struct YYLTYPE
 # define YYLTYPE_IS_DECLARED 1
 # define YYLTYPE_IS_TRIVIAL 1
 #endif
+
+
+
diff --git a/main/ast_expr2f.c b/main/ast_expr2f.c
index 49e2545459de00f3f9536f115279eedce20c0da5..9819eb7c5a1c80841429421e099124e70141b627 100644
--- a/main/ast_expr2f.c
+++ b/main/ast_expr2f.c
@@ -37,7 +37,7 @@
 #if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
 
 /* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
- * if you want the limit (max/min) macros for int types.
+ * if you want the limit (max/min) macros for int types. 
  */
 #ifndef __STDC_LIMIT_MACROS
 #define __STDC_LIMIT_MACROS 1
@@ -54,9 +54,10 @@ typedef uint32_t flex_uint32_t;
 typedef signed char flex_int8_t;
 typedef short int flex_int16_t;
 typedef int flex_int32_t;
-typedef unsigned char flex_uint8_t;
+typedef unsigned char flex_uint8_t; 
 typedef unsigned short int flex_uint16_t;
 typedef unsigned int flex_uint32_t;
+#endif /* ! C99 */
 
 /* Limits of integral types. */
 #ifndef INT8_MIN
@@ -87,8 +88,6 @@ typedef unsigned int flex_uint32_t;
 #define UINT32_MAX             (4294967295U)
 #endif
 
-#endif /* ! C99 */
-
 #endif /* ! FLEXINT_H */
 
 #ifdef __cplusplus
@@ -162,15 +161,7 @@ typedef void* yyscan_t;
 
 /* Size of default input buffer. */
 #ifndef YY_BUF_SIZE
-#ifdef __ia64__
-/* On IA-64, the buffer size is 16k, not 8k.
- * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case.
- * Ditto for the __ia64__ case accordingly.
- */
-#define YY_BUF_SIZE 32768
-#else
 #define YY_BUF_SIZE 16384
-#endif /* __ia64__ */
 #endif
 
 /* The state buf must be large enough to hold one state per character in the main buffer.
@@ -187,7 +178,7 @@ typedef struct yy_buffer_state *YY_BUFFER_STATE;
 #define EOB_ACT_LAST_MATCH 2
 
     #define YY_LESS_LINENO(n)
-
+    
 /* Return all but the first "n" matched characters back to the input stream. */
 #define yyless(n) \
 	do \
@@ -249,7 +240,7 @@ struct yy_buffer_state
 
     int yy_bs_lineno; /**< The line count. */
     int yy_bs_column; /**< The column count. */
-
+    
 	/* Whether to try to fill the input buffer when we reach the
 	 * end of it.
 	 */
@@ -602,13 +593,13 @@ struct parse_io
 	yyscan_t scanner;
 	struct ast_channel *chan;
 };
-
+ 
 void ast_yyset_column(int column_no, yyscan_t yyscanner);
 int ast_yyget_column(yyscan_t yyscanner);
 static int curlycount = 0;
 static char *expr2_token_subst(const char *mess);
 
-#line 611 "ast_expr2f.c"
+#line 600 "ast_expr2f.c"
 
 #define INITIAL 0
 #define var 1
@@ -669,9 +660,9 @@ static int yy_init_globals (yyscan_t yyscanner );
     /* This must go here because YYSTYPE and YYLTYPE are included
      * from bison output in section 1.*/
     #    define yylval yyg->yylval_r
-
+    
     #    define yylloc yyg->yylloc_r
-
+    
 int ast_yylex_init (yyscan_t* scanner);
 
 int ast_yylex_init_extra (YY_EXTRA_TYPE user_defined,yyscan_t* scanner);
@@ -710,9 +701,9 @@ YYSTYPE * ast_yyget_lval (yyscan_t yyscanner );
 void ast_yyset_lval (YYSTYPE * yylval_param ,yyscan_t yyscanner );
 
        YYLTYPE *ast_yyget_lloc (yyscan_t yyscanner );
-
+    
         void ast_yyset_lloc (YYLTYPE * yylloc_param ,yyscan_t yyscanner );
-
+    
 /* Macros after this point can all be overridden by user definitions in
  * section 1.
  */
@@ -726,7 +717,7 @@ extern int ast_yywrap (yyscan_t yyscanner );
 #endif
 
     static void yyunput (int c,char *buf_ptr  ,yyscan_t yyscanner);
-
+    
 #ifndef yytext_ptr
 static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner);
 #endif
@@ -747,12 +738,7 @@ static int input (yyscan_t yyscanner );
 
 /* Amount of stuff to slurp up with each read. */
 #ifndef YY_READ_BUF_SIZE
-#ifdef __ia64__
-/* On IA-64, the buffer size is 16k, not 8k */
-#define YY_READ_BUF_SIZE 16384
-#else
 #define YY_READ_BUF_SIZE 8192
-#endif /* __ia64__ */
 #endif
 
 /* Copy whatever the last rule matched to the standard output. */
@@ -760,7 +746,7 @@ static int input (yyscan_t yyscanner );
 /* This used to be an fputs(), but since the string might contain NUL's,
  * we now use fwrite().
  */
-#define ECHO do { if (fwrite( yytext, yyleng, 1, yyout )) {} } while (0)
+#define ECHO fwrite( yytext, yyleng, 1, yyout )
 #endif
 
 /* Gets input and stuffs it into "buf".  number of characters read, or YY_NULL,
@@ -771,7 +757,7 @@ static int input (yyscan_t yyscanner );
 	if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
 		{ \
 		int c = '*'; \
-		size_t n; \
+		int n; \
 		for ( n = 0; n < max_size && \
 			     (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
 			buf[n] = (char) c; \
@@ -856,10 +842,10 @@ YY_DECL
 	register int yy_act;
     struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
 
-#line 130 "ast_expr2.fl"
+#line 128 "ast_expr2.fl"
 
 
-#line 862 "ast_expr2f.c"
+#line 846 "ast_expr2f.c"
 
     yylval = yylval_param;
 
@@ -950,132 +936,132 @@ do_action:	/* This label is used only to access EOF actions. */
 
 case 1:
 YY_RULE_SETUP
-#line 132 "ast_expr2.fl"
+#line 130 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_OR;}
 	YY_BREAK
 case 2:
 YY_RULE_SETUP
-#line 133 "ast_expr2.fl"
+#line 131 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_AND;}
 	YY_BREAK
 case 3:
 YY_RULE_SETUP
-#line 134 "ast_expr2.fl"
+#line 132 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_EQ;}
 	YY_BREAK
 case 4:
 YY_RULE_SETUP
-#line 135 "ast_expr2.fl"
+#line 133 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_OR;}
 	YY_BREAK
 case 5:
 YY_RULE_SETUP
-#line 136 "ast_expr2.fl"
+#line 134 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_AND;}
 	YY_BREAK
 case 6:
 YY_RULE_SETUP
-#line 137 "ast_expr2.fl"
+#line 135 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_EQ;}
 	YY_BREAK
 case 7:
 YY_RULE_SETUP
-#line 138 "ast_expr2.fl"
+#line 136 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_EQTILDE;}
 	YY_BREAK
 case 8:
 YY_RULE_SETUP
-#line 139 "ast_expr2.fl"
+#line 137 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_TILDETILDE;}
 	YY_BREAK
 case 9:
 YY_RULE_SETUP
-#line 140 "ast_expr2.fl"
+#line 138 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_GT;}
 	YY_BREAK
 case 10:
 YY_RULE_SETUP
-#line 141 "ast_expr2.fl"
+#line 139 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_LT;}
 	YY_BREAK
 case 11:
 YY_RULE_SETUP
-#line 142 "ast_expr2.fl"
+#line 140 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_GE;}
 	YY_BREAK
 case 12:
 YY_RULE_SETUP
-#line 143 "ast_expr2.fl"
+#line 141 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_LE;}
 	YY_BREAK
 case 13:
 YY_RULE_SETUP
-#line 144 "ast_expr2.fl"
+#line 142 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_NE;}
 	YY_BREAK
 case 14:
 YY_RULE_SETUP
-#line 145 "ast_expr2.fl"
+#line 143 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_PLUS;}
 	YY_BREAK
 case 15:
 YY_RULE_SETUP
-#line 146 "ast_expr2.fl"
+#line 144 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_COMMA;}
 	YY_BREAK
 case 16:
 YY_RULE_SETUP
-#line 147 "ast_expr2.fl"
+#line 145 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_MINUS;}
 	YY_BREAK
 case 17:
 YY_RULE_SETUP
-#line 148 "ast_expr2.fl"
+#line 146 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_MULT;}
 	YY_BREAK
 case 18:
 YY_RULE_SETUP
-#line 149 "ast_expr2.fl"
+#line 147 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_DIV;}
 	YY_BREAK
 case 19:
 YY_RULE_SETUP
-#line 150 "ast_expr2.fl"
+#line 148 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_MOD;}
 	YY_BREAK
 case 20:
 YY_RULE_SETUP
-#line 151 "ast_expr2.fl"
+#line 149 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_COND;}
 	YY_BREAK
 case 21:
 YY_RULE_SETUP
-#line 152 "ast_expr2.fl"
+#line 150 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_COMPL;}
 	YY_BREAK
 case 22:
 YY_RULE_SETUP
-#line 153 "ast_expr2.fl"
+#line 151 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_COLON;}
 	YY_BREAK
 case 23:
 YY_RULE_SETUP
-#line 154 "ast_expr2.fl"
+#line 152 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_COLONCOLON;}
 	YY_BREAK
 case 24:
 YY_RULE_SETUP
-#line 155 "ast_expr2.fl"
+#line 153 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_LP;}
 	YY_BREAK
 case 25:
 YY_RULE_SETUP
-#line 156 "ast_expr2.fl"
+#line 154 "ast_expr2.fl"
 { SET_COLUMNS; SET_STRING; return TOK_RP;}
 	YY_BREAK
 case 26:
 YY_RULE_SETUP
-#line 157 "ast_expr2.fl"
+#line 155 "ast_expr2.fl"
 {
 		/* gather the contents of ${} expressions, with trailing stuff,
 		 * into a single TOKEN.
@@ -1088,24 +1074,24 @@ YY_RULE_SETUP
 	YY_BREAK
 case 27:
 YY_RULE_SETUP
-#line 167 "ast_expr2.fl"
+#line 165 "ast_expr2.fl"
 {}
 	YY_BREAK
 case 28:
 /* rule 28 can match eol */
 YY_RULE_SETUP
-#line 168 "ast_expr2.fl"
+#line 166 "ast_expr2.fl"
 {SET_COLUMNS; SET_STRING; return TOKEN;}
 	YY_BREAK
 case 29:
 /* rule 29 can match eol */
 YY_RULE_SETUP
-#line 170 "ast_expr2.fl"
+#line 168 "ast_expr2.fl"
 {/* what to do with eol */}
 	YY_BREAK
 case 30:
 YY_RULE_SETUP
-#line 171 "ast_expr2.fl"
+#line 169 "ast_expr2.fl"
 {
 		SET_COLUMNS;
 		/* the original behavior of the expression parser was
@@ -1118,7 +1104,7 @@ YY_RULE_SETUP
 case 31:
 /* rule 31 can match eol */
 YY_RULE_SETUP
-#line 180 "ast_expr2.fl"
+#line 178 "ast_expr2.fl"
 {
 		SET_COLUMNS;
 		SET_STRING;
@@ -1128,7 +1114,7 @@ YY_RULE_SETUP
 case 32:
 /* rule 32 can match eol */
 YY_RULE_SETUP
-#line 186 "ast_expr2.fl"
+#line 184 "ast_expr2.fl"
 {
 		curlycount = 0;
 		BEGIN(var);
@@ -1138,7 +1124,7 @@ YY_RULE_SETUP
 case 33:
 /* rule 33 can match eol */
 YY_RULE_SETUP
-#line 192 "ast_expr2.fl"
+#line 190 "ast_expr2.fl"
 {
 		curlycount--;
 		if (curlycount < 0) {
@@ -1152,7 +1138,7 @@ YY_RULE_SETUP
 case 34:
 /* rule 34 can match eol */
 YY_RULE_SETUP
-#line 202 "ast_expr2.fl"
+#line 200 "ast_expr2.fl"
 {
 		curlycount++;
 		yymore();
@@ -1160,7 +1146,7 @@ YY_RULE_SETUP
 	YY_BREAK
 case 35:
 YY_RULE_SETUP
-#line 208 "ast_expr2.fl"
+#line 206 "ast_expr2.fl"
 {
 		BEGIN(0);
 		SET_COLUMNS;
@@ -1170,7 +1156,7 @@ YY_RULE_SETUP
 	YY_BREAK
 case 36:
 YY_RULE_SETUP
-#line 215 "ast_expr2.fl"
+#line 213 "ast_expr2.fl"
 {
 		curlycount = 0;
 		BEGIN(var);
@@ -1180,7 +1166,7 @@ YY_RULE_SETUP
 case 37:
 /* rule 37 can match eol */
 YY_RULE_SETUP
-#line 221 "ast_expr2.fl"
+#line 219 "ast_expr2.fl"
 {
 		char c = yytext[yyleng-1];
 		BEGIN(0);
@@ -1191,7 +1177,7 @@ YY_RULE_SETUP
 	}
 	YY_BREAK
 case YY_STATE_EOF(trail):
-#line 230 "ast_expr2.fl"
+#line 228 "ast_expr2.fl"
 {
 		BEGIN(0);
 		SET_COLUMNS;
@@ -1202,10 +1188,10 @@ case YY_STATE_EOF(trail):
 	YY_BREAK
 case 38:
 YY_RULE_SETUP
-#line 238 "ast_expr2.fl"
+#line 236 "ast_expr2.fl"
 ECHO;
 	YY_BREAK
-#line 1208 "ast_expr2f.c"
+#line 1192 "ast_expr2f.c"
 case YY_STATE_EOF(INITIAL):
 case YY_STATE_EOF(var):
 	yyterminate();
@@ -1725,7 +1711,7 @@ static void ast_yy_load_buffer_state  (yyscan_t yyscanner)
     YY_BUFFER_STATE ast_yy_create_buffer  (FILE * file, int  size , yyscan_t yyscanner)
 {
 	YY_BUFFER_STATE b;
-
+    
 	b = (YY_BUFFER_STATE) ast_yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner );
 	if ( ! b )
 		YY_FATAL_ERROR( "out of dynamic memory in ast_yy_create_buffer()" );
@@ -1769,7 +1755,7 @@ static void ast_yy_load_buffer_state  (yyscan_t yyscanner)
 #ifndef __cplusplus
 extern int isatty (int );
 #endif /* __cplusplus */
-
+    
 /* Initializes or reinitializes a buffer.
  * This function is sometimes called more than once on the same buffer,
  * such as during a ast_yyrestart() or at EOF.
@@ -1795,7 +1781,7 @@ extern int isatty (int );
     }
 
         b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;
-
+    
 	errno = oerrno;
 }
 
@@ -1901,9 +1887,9 @@ static void ast_yyensure_buffer_stack (yyscan_t yyscanner)
 								, yyscanner);
 		if ( ! yyg->yy_buffer_stack )
 			YY_FATAL_ERROR( "out of dynamic memory in ast_yyensure_buffer_stack()" );
-
+								  
 		memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*));
-
+				
 		yyg->yy_buffer_stack_max = num_to_alloc;
 		yyg->yy_buffer_stack_top = 0;
 		return;
@@ -1932,12 +1918,12 @@ static void ast_yyensure_buffer_stack (yyscan_t yyscanner)
  * @param base the character buffer
  * @param size the size in bytes of the character buffer
  * @param yyscanner The scanner object.
- * @return the newly allocated buffer state object.
+ * @return the newly allocated buffer state object. 
  */
 YY_BUFFER_STATE ast_yy_scan_buffer  (char * base, yy_size_t  size , yyscan_t yyscanner)
 {
 	YY_BUFFER_STATE b;
-
+    
 	if ( size < 2 ||
 	     base[size-2] != YY_END_OF_BUFFER_CHAR ||
 	     base[size-1] != YY_END_OF_BUFFER_CHAR )
@@ -1973,14 +1959,14 @@ YY_BUFFER_STATE ast_yy_scan_buffer  (char * base, yy_size_t  size , yyscan_t yys
  */
 YY_BUFFER_STATE ast_yy_scan_string (yyconst char * yystr , yyscan_t yyscanner)
 {
-
+    
 	return ast_yy_scan_bytes(yystr,strlen(yystr) ,yyscanner);
 }
 
 /** Setup the input buffer state to scan the given bytes. The next call to ast_yylex() will
  * scan from a @e copy of @a bytes.
- * @param yybytes the byte buffer to scan
- * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes.
+ * @param bytes the byte buffer to scan
+ * @param len the number of bytes in the buffer pointed to by @a bytes.
  * @param yyscanner The scanner object.
  * @return the newly allocated buffer state object.
  */
@@ -1990,7 +1976,7 @@ YY_BUFFER_STATE ast_yy_scan_bytes  (yyconst char * yybytes, int  _yybytes_len ,
 	char *buf;
 	yy_size_t n;
 	int i;
-
+    
 	/* Get memory for full buffer, including space for trailing EOB's. */
 	n = _yybytes_len + 2;
 	buf = (char *) ast_yyalloc(n ,yyscanner );
@@ -2058,10 +2044,10 @@ YY_EXTRA_TYPE ast_yyget_extra  (yyscan_t yyscanner)
 int ast_yyget_lineno  (yyscan_t yyscanner)
 {
     struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
-
+    
         if (! YY_CURRENT_BUFFER)
             return 0;
-
+    
     return yylineno;
 }
 
@@ -2071,10 +2057,10 @@ int ast_yyget_lineno  (yyscan_t yyscanner)
 int ast_yyget_column  (yyscan_t yyscanner)
 {
     struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
-
+    
         if (! YY_CURRENT_BUFFER)
             return 0;
-
+    
     return yycolumn;
 }
 
@@ -2135,14 +2121,14 @@ void ast_yyset_lineno (int  line_number , yyscan_t yyscanner)
 
         /* lineno is only valid if an input buffer exists. */
         if (! YY_CURRENT_BUFFER )
-           yy_fatal_error( "ast_yyset_lineno called with no buffer" , yyscanner);
-
+           yy_fatal_error( "ast_yyset_lineno called with no buffer" , yyscanner); 
+    
     yylineno = line_number;
 }
 
 /** Set the current column.
- * \param column_no line_number
- * \param yyscanner The scanner object.
+ * @param line_number
+ * @param yyscanner The scanner object.
  */
 void ast_yyset_column (int  column_no , yyscan_t yyscanner)
 {
@@ -2150,8 +2136,8 @@ void ast_yyset_column (int  column_no , yyscan_t yyscanner)
 
         /* column is only valid if an input buffer exists. */
         if (! YY_CURRENT_BUFFER )
-           yy_fatal_error( "ast_yyset_column called with no buffer" , yyscanner);
-
+           yy_fatal_error( "ast_yyset_column called with no buffer" , yyscanner); 
+    
     yycolumn = column_no;
 }
 
@@ -2204,13 +2190,13 @@ YYLTYPE *ast_yyget_lloc  (yyscan_t yyscanner)
     struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
     return yylloc;
 }
-
+    
 void ast_yyset_lloc (YYLTYPE *  yylloc_param , yyscan_t yyscanner)
 {
     struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
     yylloc = yylloc_param;
 }
-
+    
 /* User-visible API */
 
 /* ast_yylex_init is special because it creates the scanner itself, so it is
@@ -2258,20 +2244,20 @@ int ast_yylex_init_extra(YY_EXTRA_TYPE yy_user_defined,yyscan_t* ptr_yy_globals
         errno = EINVAL;
         return 1;
     }
-
+	
     *ptr_yy_globals = (yyscan_t) ast_yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts );
-
+	
     if (*ptr_yy_globals == NULL){
         errno = ENOMEM;
         return 1;
     }
-
+    
     /* By setting to 0xAA, we expose bugs in
     yy_init_globals. Leave at 0x00 for releases. */
     memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t));
-
+    
     ast_yyset_extra (yy_user_defined, *ptr_yy_globals);
-
+    
     return yy_init_globals ( *ptr_yy_globals );
 }
 
@@ -2381,7 +2367,7 @@ void *ast_yyrealloc  (void * ptr, yy_size_t  size , yyscan_t yyscanner)
 
 #define YYTABLES_NAME "yytables"
 
-#line 238 "ast_expr2.fl"
+#line 236 "ast_expr2.fl"
 
 
 
@@ -2486,7 +2472,7 @@ void  ast_expr_clear_extra_error_info(void)
        extra_error_message[0] = 0;
 }
 
-static const char * const expr2_token_equivs1[] =
+static const char * const expr2_token_equivs1[] = 
 {
 	"TOKEN",
 	"TOK_COND",
@@ -2512,7 +2498,7 @@ static const char * const expr2_token_equivs1[] =
 	"TOK_LP"
 };
 
-static const char * const expr2_token_equivs2[] =
+static const char * const expr2_token_equivs2[] = 
 {
 	"<token>",
 	"?",
@@ -2623,3 +2609,4 @@ int ast_yyerror (const char *s,  yyltype *loc, struct parse_io *parseio )
 	free(s2);
 	return(0);
 }
+
diff --git a/main/asterisk.c b/main/asterisk.c
index de520a0b0bab6231c69473027b557b24e3c762fc..dea849f10c1f0a6fdcd64ebcd9e82fcab0c43e30 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -297,7 +297,7 @@ int daemon(int, int);  /* defined in libresolv of all places */
 #define NUM_MSGS 64
 
 /*! Displayed copyright tag */
-#define COPYRIGHT_TAG "Copyright (C) 1999 - 2021, Sangoma Technologies Corporation and others."
+#define COPYRIGHT_TAG "Copyright (C) 1999 - 2022, Sangoma Technologies Corporation and others."
 
 /*! \brief Welcome message when starting a CLI interface */
 #define WELCOME_MESSAGE \
@@ -3007,6 +3007,23 @@ static char *cli_complete(EditLine *editline, int ch)
 			/* Only read 1024 bytes at a time */
 			res = read(ast_consock, mbuf + mlen, 1024);
 			if (res > 0) {
+				if (!strncmp(mbuf, "Usage:", 6)) {
+					/*
+					 * Abort on malformed tab completes
+					 * If help (tab complete) follows certain
+					 * special characters, the main Asterisk process
+					 * provides usage for the internal tab complete
+					 * helper command that the remote console processes
+					 * use.
+					 * If this happens, the AST_CLI_COMPLETE_EOF sentinel
+					 * value never gets sent. As a result, we'll just block
+					 * forever if we don't handle this case.
+					 * If we get command usage on a tab complete, then
+					 * we know this scenario just happened and we should
+					 * just silently ignore and do nothing.
+					 */
+					break;
+				}
 				mlen += res;
 				mbuf[mlen] = '\0';
 			}
@@ -3351,7 +3368,7 @@ static int show_cli_help(void)
 	printf("   -L <load>       Limit the maximum load average before rejecting new calls\n");
 	printf("   -M <value>      Limit the maximum number of calls to the specified value\n");
 	printf("   -m              Mute debugging and console output on the console\n");
-	printf("   -n              Disable console colorization\n");
+	printf("   -n              Disable console colorization. Can be used only at startup.\n");
 	printf("   -p              Run as pseudo-realtime thread\n");
 	printf("   -q              Quiet mode (suppress output)\n");
 	printf("   -r              Connect to Asterisk on this machine\n");
@@ -3360,7 +3377,7 @@ static int show_cli_help(void)
 	printf("   -t              Record soundfiles in /var/tmp and move them where they\n");
 	printf("                   belong after they are done\n");
 	printf("   -T              Display the time in [Mmm dd hh:mm:ss] format for each line\n");
-	printf("                   of output to the CLI\n");
+	printf("                   of output to the CLI. Cannot be used with remote console mode.\n\n");
 	printf("   -v              Increase verbosity (multiple v's = more verbose)\n");
 	printf("   -x <cmd>        Execute command <cmd> (implies -r)\n");
 	printf("   -X              Enable use of #exec in asterisk.conf\n");
@@ -3554,7 +3571,7 @@ int main(int argc, char *argv[])
 	}
 	ast_mainpid = getpid();
 
-	/* Process command-line options that effect asterisk.conf load. */
+	/* Process command-line options that affect asterisk.conf load. */
 	while ((c = getopt(argc, argv, getopt_settings)) != -1) {
 		switch (c) {
 		case 'X':
@@ -3716,6 +3733,57 @@ int main(int argc, char *argv[])
 		}
 	}
 
+	if (ast_opt_remote) {
+		int didwarn = 0;
+		optind = 1;
+
+		/* Not all options can be used with remote console. Warn if they're used. */
+		while ((c = getopt(argc, argv, getopt_settings)) != -1) {
+			switch (c) {
+			/* okay to run with remote console */
+			case 'B': /* force black background */
+			case 'C': /* set config path */
+			case 'd': /* debug */
+			case 'h': /* help */
+			case 'I': /* obsolete timing option: warning already thrown if used */
+			case 'L': /* max load */
+			case 'M': /* max calls */
+			case 'm': /* mute */
+			/*! \note The q option is never used anywhere, only defined */
+			case 'q': /* quiet */
+			case 'R': /* reconnect */
+			case 'r': /* remote */
+			/*! \note Can ONLY be used with remote console */
+			case 's': /* set socket path */
+			case 'V': /* version */
+			case 'v': /* verbose */
+			case 'W': /* white background */
+			case 'x': /* remote execute */
+			case '?': /* ? */
+				break;
+			/* can only be run when Asterisk is starting */
+			case 'X': /* enables #exec for asterisk.conf only. */
+			case 'c': /* foreground console */
+			case 'e': /* minimum memory free */
+			case 'F': /* always fork */
+			case 'f': /* no fork */
+			case 'G': /* run group */
+			case 'g': /* dump core */
+			case 'i': /* init keys */
+			case 'n': /* no color */
+			case 'p': /* high priority */
+			case 'T': /* timestamp */
+			case 't': /* cache record files */
+			case 'U': /* run user */
+				fprintf(stderr, "'%c' option is not compatible with remote console mode and has no effect.\n", c);
+				didwarn = 1;
+			}
+		}
+		if (didwarn) {
+			fprintf(stderr, "\n"); /* if any warnings print out, make them stand out */
+		}
+	}
+
 	/* For remote connections, change the name of the remote connection.
 	 * We do this for the benefit of init scripts (which need to know if/when
 	 * the main asterisk process has died yet). */
@@ -4014,7 +4082,7 @@ static void asterisk_daemon(int isroot, const char *runuser, const char *rungrou
 
 	load_astmm_phase_1();
 
-	/* Check whether high prio was succesfully set by us or some
+	/* Check whether high prio was successfully set by us or some
 	 * other incantation. */
 	if (has_priority()) {
 		ast_set_flag(&ast_options, AST_OPT_FLAG_HIGH_PRIORITY);
@@ -4201,6 +4269,7 @@ static void asterisk_daemon(int isroot, const char *runuser, const char *rungrou
 
 	/* loads the cli_permissions.conf file needed to implement cli restrictions. */
 	ast_cli_perms_init(0);
+	ast_cli_channels_init(); /* Not always safe to access CLI commands until startup is complete. */
 
 	ast_stun_init();
 
diff --git a/main/audiohook.c b/main/audiohook.c
index 966d5f2f6b3d91666820551eb7bffe759e0e5a51..102c0d0c61ff4061e5d9c72f734e9e5374a82a0d 100644
--- a/main/audiohook.c
+++ b/main/audiohook.c
@@ -34,12 +34,12 @@
 #include "asterisk/channel.h"
 #include "asterisk/utils.h"
 #include "asterisk/lock.h"
-#include "asterisk/linkedlists.h"
 #include "asterisk/audiohook.h"
 #include "asterisk/slinfactory.h"
 #include "asterisk/frame.h"
 #include "asterisk/translate.h"
 #include "asterisk/format_cache.h"
+#include "asterisk/test.h"
 
 #define AST_AUDIOHOOK_SYNC_TOLERANCE 100 /*!< Tolerance in milliseconds for audiohooks synchronization */
 #define AST_AUDIOHOOK_SMALL_QUEUE_TOLERANCE 100 /*!< When small queue is enabled, this is the maximum amount of audio that can remain queued at a time. */
@@ -109,6 +109,9 @@ int ast_audiohook_init(struct ast_audiohook *audiohook, enum ast_audiohook_type
 
 	audiohook->init_flags = init_flags;
 
+	/* Set direction to BOTH so that we feed frames in both directions */
+	audiohook->direction = AST_AUDIOHOOK_DIRECTION_BOTH;
+
 	/* initialize internal rate at 8khz, this will adjust if necessary */
 	audiohook_set_internal_rate(audiohook, DEFAULT_INTERNAL_SAMPLE_RATE, 0);
 
@@ -144,6 +147,18 @@ int ast_audiohook_destroy(struct ast_audiohook *audiohook)
 	return 0;
 }
 
+int ast_audiohook_set_frame_feed_direction(struct ast_audiohook *audiohook, enum ast_audiohook_direction direction)
+{
+	/* Only set the direction on new audiohooks */
+	if (audiohook->status != AST_AUDIOHOOK_STATUS_NEW) {
+		ast_debug(3, "Can not set direction on attached Audiohook %p\n", audiohook);
+		return -1;
+	}
+
+	audiohook->direction = direction;
+	return 0;
+}
+
 #define SHOULD_MUTE(hook, dir) \
 	((ast_test_flag(hook, AST_AUDIOHOOK_MUTE_READ) && (dir == AST_AUDIOHOOK_DIRECTION_READ)) || \
 	(ast_test_flag(hook, AST_AUDIOHOOK_MUTE_WRITE) && (dir == AST_AUDIOHOOK_DIRECTION_WRITE)) || \
@@ -159,6 +174,13 @@ int ast_audiohook_write_frame(struct ast_audiohook *audiohook, enum ast_audiohoo
 	int other_factory_samples;
 	int other_factory_ms;
 
+	/* Don't feed the frame if we are set to read and this is a write frame or if set to
+	   write and this is a read frame as we don't want it. Plus, it can cause mis-resampling
+	   if the READ and WRITE frames have different bitrates */
+	if (audiohook->direction != AST_AUDIOHOOK_DIRECTION_BOTH && audiohook->direction != direction) {
+		return 0;
+	}
+
 	/* Update last feeding time to be current */
 	*rwtime = ast_tvnow();
 
@@ -1354,3 +1376,33 @@ int ast_audiohook_set_mute(struct ast_channel *chan, const char *source, enum as
 
 	return (audiohook ? 0 : -1);
 }
+
+int ast_audiohook_set_mute_all(struct ast_channel *chan, const char *source, enum ast_audiohook_flags flag, int clearmute)
+{
+	struct ast_audiohook *audiohook = NULL;
+	int count = 0;
+
+	ast_channel_lock(chan);
+
+	if (!ast_channel_audiohooks(chan)) {
+		return -1;
+	}
+
+	AST_LIST_TRAVERSE(&ast_channel_audiohooks(chan)->spy_list, audiohook, list) {
+		if (!strcasecmp(audiohook->source, source)) {
+			count++;
+			if (clearmute) {
+				ast_clear_flag(audiohook, flag);
+			} else {
+				ast_set_flag(audiohook, flag);
+			}
+		}
+	}
+
+	ast_test_suite_event_notify("AUDIOHOOK_GROUP_MUTE_TOGGLE", "Channel: %s\r\nSource: %s\r\nCount: %d\r\n",
+									ast_channel_name(chan), source, count);
+
+	ast_channel_unlock(chan);
+
+	return count;
+}
diff --git a/main/bridge.c b/main/bridge.c
index 289c48bc09343fd1dd9e758374f8a2c6dc53c267..112b621b43b5bdd15dd52d74e0344c8973c5449e 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -2525,7 +2525,7 @@ int ast_bridge_add_channel(struct ast_bridge *bridge, struct ast_channel *chan,
 		if (ast_bridge_impart(bridge, yanked_chan, NULL, features,
 			AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
 			/* It is possible for us to yank a channel and have some other
-			 * thread start a PBX on the channl after we yanked it. In particular,
+			 * thread start a PBX on the channel after we yanked it. In particular,
 			 * this can theoretically happen on the ;2 of a Local channel if we
 			 * yank it prior to the ;1 being answered. Make sure that it isn't
 			 * executing a PBX before hanging it up.
diff --git a/main/bridge_basic.c b/main/bridge_basic.c
index df211957f6a7de2930c4a8292197cb98aad8a18e..2d3872df640db2fc9b7b821ef304da7611273672 100644
--- a/main/bridge_basic.c
+++ b/main/bridge_basic.c
@@ -1397,6 +1397,27 @@ static const char *get_transfer_context(struct ast_channel *transferer, const ch
 	return "default";
 }
 
+/*!
+ * \internal
+ * \brief Determine the transfer extension to use.
+ *
+ * \param transferer Channel initiating the transfer.
+ * \param exten User supplied extension if available.  May be NULL.
+ *
+ * \return The extension to use for the transfer.
+ */
+static const char *get_transfer_exten(struct ast_channel *transferer, const char *exten)
+{
+	if (!ast_strlen_zero(exten)) {
+		return exten;
+	}
+	exten = pbx_builtin_getvar_helper(transferer, "TRANSFER_EXTEN");
+	if (!ast_strlen_zero(exten)) {
+		return exten;
+	}
+	return ""; /* empty default, to get transfer extension from user now */
+}
+
 /*!
  * \brief Allocate and initialize attended transfer properties
  *
@@ -3162,10 +3183,25 @@ static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len
 	int attempts = 0;
 	int max_attempts;
 	struct ast_features_xfer_config *xfer_cfg;
-	char *retry_sound;
-	char *invalid_sound;
+	char *announce_sound, *retry_sound, *invalid_sound;
+	const char *extenoverride;
 
 	ast_channel_lock(chan);
+	extenoverride = get_transfer_exten(chan, NULL);
+
+	if (!ast_strlen_zero(extenoverride)) {
+		int extenres = ast_exists_extension(chan, context, extenoverride, 1,
+			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL)) ? 1 : 0;
+		if (extenres) {
+			ast_copy_string(exten, extenoverride, exten_len);
+			ast_channel_unlock(chan);
+			ast_verb(3, "Transfering call to '%s@%s'", exten, context);
+			return 0;
+		}
+		ast_log(LOG_WARNING, "Override extension '%s' does not exist in context '%s'\n", extenoverride, context);
+		/* since we didn't get a valid extension from the channel, fall back and grab it from the user as usual now */
+	}
+
 	xfer_cfg = ast_get_chan_features_xfer_config(chan);
 	if (!xfer_cfg) {
 		ast_log(LOG_ERROR, "Channel %s: Unable to get transfer configuration\n",
@@ -3175,21 +3211,24 @@ static int grab_transfer(struct ast_channel *chan, char *exten, size_t exten_len
 	}
 	digit_timeout = xfer_cfg->transferdigittimeout * 1000;
 	max_attempts = xfer_cfg->transferdialattempts;
+	announce_sound = ast_strdupa(xfer_cfg->transferannouncesound);
 	retry_sound = ast_strdupa(xfer_cfg->transferretrysound);
 	invalid_sound = ast_strdupa(xfer_cfg->transferinvalidsound);
 	ao2_ref(xfer_cfg, -1);
 	ast_channel_unlock(chan);
 
 	/* Play the simple "transfer" prompt out and wait */
-	res = ast_stream_and_wait(chan, "pbx-transfer", AST_DIGIT_ANY);
-	ast_stopstream(chan);
-	if (res < 0) {
-		/* Hangup or error */
-		return -1;
-	}
-	if (res) {
-		/* Store the DTMF digit that interrupted playback of the file. */
-		exten[0] = res;
+	if (!ast_strlen_zero(announce_sound)) {
+		res = ast_stream_and_wait(chan, announce_sound, AST_DIGIT_ANY);
+		ast_stopstream(chan);
+		if (res < 0) {
+			/* Hangup or error */
+			return -1;
+		}
+		if (res) {
+			/* Store the DTMF digit that interrupted playback of the file. */
+			exten[0] = res;
+		}
 	}
 
 	/* Drop to dialtone so they can enter the extension they want to transfer to */
diff --git a/main/bridge_channel.c b/main/bridge_channel.c
index c496289012f73f7859fda1b1d6bbd920d3b26504..56c81843abbf4a4af1de5e65b2434756209c96bd 100644
--- a/main/bridge_channel.c
+++ b/main/bridge_channel.c
@@ -682,7 +682,7 @@ static int bridge_channel_write_frame(struct ast_bridge_channel *bridge_channel,
 			 */
 			return 0;
 		case AST_FRAME_CNG:
-            break;
+			break;
 		case AST_FRAME_DTMF_BEGIN:
 		case AST_FRAME_DTMF_END:
 			/*
@@ -1181,23 +1181,7 @@ static int run_app_helper(struct ast_channel *chan, const char *app_name, const
 	} else if (!strcasecmp("Macro", app_name)) {
 		ast_app_exec_macro(NULL, chan, app_args);
 	} else {
-		struct ast_app *app;
-
-		app = pbx_findapp(app_name);
-		if (!app) {
-			ast_log(LOG_WARNING, "Could not find application (%s)\n", app_name);
-		} else {
-			struct ast_str *substituted_args = ast_str_create(16);
-
-			if (substituted_args) {
-				ast_str_substitute_variables(&substituted_args, 0, chan, app_args);
-				res = pbx_exec(chan, app, ast_str_buffer(substituted_args));
-				ast_free(substituted_args);
-			} else {
-				ast_log(LOG_WARNING, "Could not substitute application argument variables for %s\n", app_name);
-				res = pbx_exec(chan, app, app_args);
-			}
-		}
+		res = ast_pbx_exec_application(chan, app_name, app_args);
 	}
 	return res;
 }
diff --git a/main/callerid.c b/main/callerid.c
index 4d6186b3bccd4c93cea95b2cf6bf5cb8e45f24e3..c6c29a002a649ed7757f8401eaa8bf08b7863a4f 100644
--- a/main/callerid.c
+++ b/main/callerid.c
@@ -736,7 +736,8 @@ void callerid_free(struct callerid_state *cid)
 	ast_free(cid);
 }
 
-static int callerid_genmsg(char *msg, int size, const char *number, const char *name, int flags)
+static int callerid_genmsg(char *msg, int size, const char *number, const char *name, int flags, int format,
+	const char *ddn, int redirecting)
 {
 	struct timeval now = ast_tvnow();
 	struct ast_tm tm;
@@ -754,6 +755,7 @@ static int callerid_genmsg(char *msg, int size, const char *number, const char *
 				tm.tm_mday, tm.tm_hour, tm.tm_min);
 	size -= res;
 	ptr += res;
+
 	if (ast_strlen_zero(number) || (flags & CID_UNKNOWN_NUMBER)) {
 		/* Indicate number not known */
 		res = snprintf(ptr, size, "\004\001O");
@@ -779,6 +781,11 @@ static int callerid_genmsg(char *msg, int size, const char *number, const char *
 		size -= i;
 	}
 
+	if (format == CID_TYPE_SDMF) { /* If Simple Data Message Format, we're done. */
+		/* (some older Caller ID units only support SDMF. If they get an MDMF spill, it's useless.) */
+		return (ptr - msg);
+	}
+
 	if (ast_strlen_zero(name) || (flags & CID_UNKNOWN_NAME)) {
 		/* Indicate name not known */
 		res = snprintf(ptr, size, "\010\001O");
@@ -803,8 +810,44 @@ static int callerid_genmsg(char *msg, int size, const char *number, const char *
 		ptr += i;
 		size -= i;
 	}
-	return (ptr - msg);
 
+	/* Call Qualifier */
+	if (flags & CID_QUALIFIER) {
+		res = snprintf(ptr, size, "\006\001L"); /* LDC (Long Distance Call) is the only valid option */
+		size -= res;
+		ptr += res;
+	}
+
+	/* DDN (Dialable Directory Number) - 11 digits MAX, parameter 003 */
+	/* some CPE seem to display the DDN instead of the CLID, if sent */
+
+	/* Redirecting Reason */
+	if (redirecting >= 0) {
+		res = 0;
+		switch (redirecting) {
+		case AST_REDIRECTING_REASON_USER_BUSY:
+			res = snprintf(ptr, size, "\005\001\001");
+			break;
+		case AST_REDIRECTING_REASON_NO_ANSWER:
+			res = snprintf(ptr, size, "\005\001\002");
+			break;
+		case AST_REDIRECTING_REASON_UNCONDITIONAL:
+			res = snprintf(ptr, size, "\005\001\003");
+			break;
+		case AST_REDIRECTING_REASON_CALL_FWD_DTE:
+			res = snprintf(ptr, size, "\005\001\004");
+			break;
+		case AST_REDIRECTING_REASON_DEFLECTION:
+			res = snprintf(ptr, size, "\005\001\005");
+			break;
+		default:
+			break;
+		}
+		ptr += res;
+		size -= res;
+	}
+
+	return (ptr - msg);
 }
 
 int ast_callerid_vmwi_generate(unsigned char *buf, int active, int type, struct ast_format *codec,
@@ -824,7 +867,7 @@ int ast_callerid_vmwi_generate(unsigned char *buf, int active, int type, struct
 		msg[0] = 0x82;
 
 		/* put date, number info at the right place */
-		len = callerid_genmsg(msg+2, sizeof(msg)-2, number, name, flags);
+		len = callerid_genmsg(msg+2, sizeof(msg)-2, number, name, flags, CID_TYPE_MDMF, "", -1);
 
 		/* length of MDMF CLI plus Message Waiting Structure */
 		msg[1] = len+3;
@@ -896,6 +939,12 @@ int ast_callerid_vmwi_generate(unsigned char *buf, int active, int type, struct
 }
 
 int callerid_generate(unsigned char *buf, const char *number, const char *name, int flags, int callwaiting, struct ast_format *codec)
+{
+	return callerid_full_generate(buf, number, name, NULL, -1, flags, CID_TYPE_MDMF, callwaiting, codec);
+}
+
+int callerid_full_generate(unsigned char *buf, const char *number, const char *name, const char *ddn, int redirecting,
+	int flags, int format, int callwaiting, struct ast_format *codec)
 {
 	int bytes = 0;
 	int x, sum;
@@ -906,7 +955,7 @@ int callerid_generate(unsigned char *buf, const char *number, const char *name,
 	float ci = 0.0;
 	float scont = 0.0;
 	char msg[256];
-	len = callerid_genmsg(msg, sizeof(msg), number, name, flags);
+	len = callerid_genmsg(msg, sizeof(msg), number, name, flags, format, ddn, redirecting);
 	if (!callwaiting) {
 		/* Wait a half a second */
 		for (x = 0; x < 4000; x++)
@@ -1051,23 +1100,56 @@ int ast_callerid_parse(char *input_str, char **name, char **location)
 	return 0;
 }
 
-static int __ast_callerid_generate(unsigned char *buf, const char *name, const char *number, int callwaiting, struct ast_format *codec)
+static int __ast_callerid_generate(unsigned char *buf, const char *name, const char *number,
+	const char *ddn, int redirecting, int pres, int qualifier, int format, int callwaiting, struct ast_format *codec)
 {
+	int flags = 0;
+
+	ast_debug(1, "Caller ID Type %s: Number: %s, Name: %s, Redirecting No: %s, Redirecting Reason: %s, Pres: %s, Qualifier: %s, Format: %s\n",
+		callwaiting ? "II" : "I", number, name, ddn, ast_redirecting_reason_describe(redirecting),
+		ast_named_caller_presentation(pres), qualifier ? "LDC" : "None", format == CID_TYPE_MDMF ? "MDMF" : "SDMF");
+
 	if (ast_strlen_zero(name))
 		name = NULL;
 	if (ast_strlen_zero(number))
 		number = NULL;
-	return callerid_generate(buf, number, name, 0, callwaiting, codec);
+
+	if (pres & AST_PRES_RESTRICTED) {
+		flags |= CID_PRIVATE_NUMBER;
+		flags |= CID_PRIVATE_NAME;
+	} else if (pres & AST_PRES_UNAVAILABLE) {
+		flags |= CID_UNKNOWN_NUMBER;
+		flags |= CID_UNKNOWN_NAME;
+	}
+
+	if (qualifier) {
+		flags |= CID_QUALIFIER;
+	}
+
+	return callerid_full_generate(buf, number, name, ddn, redirecting, flags, format, callwaiting, codec);
 }
 
 int ast_callerid_generate(unsigned char *buf, const char *name, const char *number, struct ast_format *codec)
 {
-	return __ast_callerid_generate(buf, name, number, 0, codec);
+	return __ast_callerid_generate(buf, name, number, "", -1, 0, 0, CID_TYPE_MDMF, 0, codec);
 }
 
 int ast_callerid_callwaiting_generate(unsigned char *buf, const char *name, const char *number, struct ast_format *codec)
 {
-	return __ast_callerid_generate(buf, name, number, 1, codec);
+	return __ast_callerid_generate(buf, name, number, "", -1, 0, 0, CID_TYPE_MDMF, 1, codec);
+}
+
+int ast_callerid_full_generate(unsigned char *buf, const char *name, const char *number,
+	const char *ddn, int redirecting, int pres, int qualifier, int format, struct ast_format *codec)
+{
+	return __ast_callerid_generate(buf, name, number, ddn, redirecting, pres, qualifier, format, 0, codec);
+}
+
+int ast_callerid_callwaiting_full_generate(unsigned char *buf, const char *name, const char *number,
+	const char *ddn, int redirecting, int pres, int qualifier, struct ast_format *codec)
+{
+	/* Type II Caller ID (CWCID) only uses MDMF, so format isn't an argument */
+	return __ast_callerid_generate(buf, name, number, ddn, redirecting, pres, qualifier, CID_TYPE_MDMF, 1, codec);
 }
 
 char *ast_callerid_merge(char *buf, int bufsiz, const char *name, const char *num, const char *unknown)
diff --git a/main/cdr.c b/main/cdr.c
index f3e2a8f39597cf5b13498cbeff85d98e65d08ce6..d0536c37e11b549f40ed9d97ef3015e1fb058ab5 100644
--- a/main/cdr.c
+++ b/main/cdr.c
@@ -111,6 +111,29 @@
 					to undisable (enable) CDR for a call.</para>
 					</description>
 				</configOption>
+				<configOption name="ignorestatechanges" default="no">
+					<synopsis>Whether CDR is updated or forked by bridging changes.</synopsis>
+					<description><para>Define whether or not CDR should be updated by bridging changes.
+					This includes entering and leaving bridges and call parking.</para>
+					<para>If this is set to "no", bridging changes will be ignored for all CDRs.
+					This should only be done if these events should not affect CDRs and are undesired,
+					such as to use a single CDR for the lifetime of the channel.</para>
+					<para>This setting cannot be changed on a reload.</para>
+					</description>
+				</configOption>
+				<configOption name="ignoredialchanges" default="no">
+					<synopsis>Whether CDR is updated or forked by dial updates.</synopsis>
+					<description><para>Define whether or not CDR should be updated by dial updates.</para>
+					<para>If this is set to "no", a single CDR will be used for the channel, even if
+					multiple endpoints or destinations are dialed sequentially. Note that you will also
+					lose detailed nonanswer dial dispositions if this option is enabled, which may not be acceptable,
+					e.g. instead of detailed no-answer dispositions like BUSY and CONGESTION, the disposition
+					will always be NO ANSWER if the channel was unanswered (it will still be ANSWERED
+					if the channel was answered).</para>
+					<para>This option should be enabled if a single CDR is desired for the lifetime of
+					the channel.</para>
+					</description>
+				</configOption>
 				<configOption name="unanswered">
 					<synopsis>Log calls that are never answered and don't set an outgoing party.</synopsis>
 					<description><para>
@@ -208,6 +231,8 @@
 #define DEFAULT_END_BEFORE_H_EXTEN "1"
 #define DEFAULT_INITIATED_SECONDS "0"
 #define DEFAULT_CHANNEL_ENABLED "1"
+#define DEFAULT_IGNORE_STATE_CHANGES "0"
+#define DEFAULT_IGNORE_DIAL_CHANGES "0"
 
 #define DEFAULT_BATCH_SIZE "100"
 #define MAX_BATCH_SIZE 1000
@@ -222,6 +247,7 @@
 	} while (0)
 
 static int cdr_debug_enabled;
+static int dial_changes_ignored;
 
 #define CDR_DEBUG(fmt, ...) \
 	do { \
@@ -1403,7 +1429,6 @@ static struct ast_cdr *cdr_object_create_public_records(struct cdr_object *cdr)
 			if (party_b->rtp_stats)
 				cdr_copy->rtp_stats = party_b->rtp_stats;
 		}
-
 		if (ast_strlen_zero(cdr_copy->userfield) && !ast_strlen_zero(it_cdr->party_a.userfield)) {
 			ast_copy_string(cdr_copy->userfield, it_cdr->party_a.userfield, sizeof(cdr_copy->userfield));
 		}
@@ -2229,6 +2254,10 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
 			if (!it_cdr->fn_table->process_dial_begin) {
 				continue;
 			}
+			if (dial_changes_ignored) {
+				CDR_DEBUG("%p - Ignoring Dial Begin message\n", it_cdr);
+				continue;
+			}
 			CDR_DEBUG("%p - Processing Dial Begin message for channel %s, peer %s\n",
 				it_cdr,
 				caller ? caller->base->name : "(none)",
@@ -2240,6 +2269,12 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
 			if (!it_cdr->fn_table->process_dial_end) {
 				continue;
 			}
+			if (dial_changes_ignored) {
+				/* Set the disposition, and do nothing else. */
+				it_cdr->disposition = dial_status_to_disposition(dial_status);
+				CDR_DEBUG("%p - Setting disposition and that's it (%s)\n", it_cdr, dial_status);
+				continue;
+			}
 			CDR_DEBUG("%p - Processing Dial End message for channel %s, peer %s\n",
 				it_cdr,
 				caller ? caller->base->name : "(none)",
@@ -2251,15 +2286,19 @@ static void handle_dial_message(void *data, struct stasis_subscription *sub, str
 		}
 	}
 
-	/* If no CDR handled a dial begin message, make a new one */
-	if (res && ast_strlen_zero(dial_status)) {
-		struct cdr_object *new_cdr;
+	/* If we're ignoring dial changes, don't allow multiple CDRs for this channel. */
+	if (!dial_changes_ignored) {
+		/* If no CDR handled a dial begin message, make a new one */
+		if (res && ast_strlen_zero(dial_status)) {
+			struct cdr_object *new_cdr;
 
-		new_cdr = cdr_object_create_and_append(cdr, stasis_message_timestamp(message));
-		if (new_cdr) {
-			new_cdr->fn_table->process_dial_begin(new_cdr, caller, peer);
+			new_cdr = cdr_object_create_and_append(cdr, stasis_message_timestamp(message));
+			if (new_cdr) {
+				new_cdr->fn_table->process_dial_begin(new_cdr, caller, peer);
+			}
 		}
 	}
+
 	ao2_unlock(cdr);
 	ao2_cleanup(cdr);
 }
@@ -2354,6 +2393,7 @@ static void handle_channel_snapshot_update_message(void *data, struct stasis_sub
 	if (filter_channel_snapshot_message(update->old_snapshot, update->new_snapshot)) {
 		return;
 	}
+
 	if (update->new_snapshot && !update->old_snapshot) {
 		struct module_config *mod_cfg = NULL;
 
@@ -2435,6 +2475,7 @@ static void handle_channel_snapshot_update_message(void *data, struct stasis_sub
 		ao2_callback_data(active_cdrs_all, OBJ_NODATA | OBJ_MULTIPLE | OBJ_SEARCH_KEY,
 			cdr_object_finalize_party_b, (char *) update->new_snapshot->base->name, update->new_snapshot);
 	}
+
 	ao2_cleanup(cdr);
 }
 
@@ -4349,6 +4390,8 @@ static char *handle_cli_status(struct ast_cli_entry *e, int cmd, struct ast_cli_
 		ast_cli(a->fd, "  Log calls by default:       %s\n", ast_test_flag(&mod_cfg->general->settings, CDR_CHANNEL_DEFAULT_ENABLED) ? "Yes" : "No");
 		ast_cli(a->fd, "  Log unanswered calls:       %s\n", ast_test_flag(&mod_cfg->general->settings, CDR_UNANSWERED) ? "Yes" : "No");
 		ast_cli(a->fd, "  Log congestion:             %s\n\n", ast_test_flag(&mod_cfg->general->settings, CDR_CONGESTION) ? "Yes" : "No");
+		ast_cli(a->fd, "  Ignore bridging changes:    %s\n\n", ast_test_flag(&mod_cfg->general->settings, CDR_IGNORE_STATE_CHANGES) ? "Yes" : "No");
+		ast_cli(a->fd, "  Ignore dial state changes:  %s\n\n", ast_test_flag(&mod_cfg->general->settings, CDR_IGNORE_DIAL_CHANGES) ? "Yes" : "No");
 		if (ast_test_flag(&mod_cfg->general->settings, CDR_BATCHMODE)) {
 			ast_cli(a->fd, "* Batch Mode Settings\n");
 			ast_cli(a->fd, "  -------------------\n");
@@ -4528,6 +4571,8 @@ static int process_config(int reload)
 		aco_option_register(&cfg_info, "size", ACO_EXACT, general_options, DEFAULT_BATCH_SIZE, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_cdr_config, batch_settings.size), 0, MAX_BATCH_SIZE);
 		aco_option_register(&cfg_info, "time", ACO_EXACT, general_options, DEFAULT_BATCH_TIME, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_cdr_config, batch_settings.time), 1, MAX_BATCH_TIME);
 		aco_option_register(&cfg_info, "channeldefaultenabled", ACO_EXACT, general_options, DEFAULT_CHANNEL_ENABLED, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_CHANNEL_DEFAULT_ENABLED);
+		aco_option_register(&cfg_info, "ignorestatechanges", ACO_EXACT, general_options, DEFAULT_IGNORE_STATE_CHANGES, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_IGNORE_STATE_CHANGES);
+		aco_option_register(&cfg_info, "ignoredialchanges", ACO_EXACT, general_options, DEFAULT_IGNORE_DIAL_CHANGES, OPT_BOOLFLAG_T, 1, FLDSET(struct ast_cdr_config, settings), CDR_IGNORE_DIAL_CHANGES);
 	}
 
 	if (aco_process_config(&cfg_info, reload) == ACO_PROCESS_ERROR) {
@@ -4690,6 +4735,7 @@ static int unload_module(void)
 
 static int load_module(void)
 {
+	struct module_config *mod_cfg = NULL;
 	if (process_config(0)) {
 		return AST_MODULE_LOAD_FAILURE;
 	}
@@ -4710,13 +4756,36 @@ static int load_module(void)
 		return AST_MODULE_LOAD_FAILURE;
 	}
 
+	mod_cfg = ao2_global_obj_ref(module_configs);
+
 	stasis_message_router_add(stasis_router, ast_channel_snapshot_type(), handle_channel_snapshot_update_message, NULL);
+
+	/* Always process dial messages, because even if we ignore most of it, we do want the dial status for the disposition. */
 	stasis_message_router_add(stasis_router, ast_channel_dial_type(), handle_dial_message, NULL);
-	stasis_message_router_add(stasis_router, ast_channel_entered_bridge_type(), handle_bridge_enter_message, NULL);
-	stasis_message_router_add(stasis_router, ast_channel_left_bridge_type(), handle_bridge_leave_message, NULL);
-	stasis_message_router_add(stasis_router, ast_parked_call_type(), handle_parked_call_message, NULL);
+	if (!mod_cfg || !ast_test_flag(&mod_cfg->general->settings, CDR_IGNORE_DIAL_CHANGES)) {
+		dial_changes_ignored = 0;
+	} else {
+		dial_changes_ignored = 1;
+		CDR_DEBUG("Dial messages will be mostly ignored\n");
+	}
+
+	/* If explicitly instructed to ignore call state changes, then ignore bridging events, parking, etc. */
+	if (!mod_cfg || !ast_test_flag(&mod_cfg->general->settings, CDR_IGNORE_STATE_CHANGES)) {
+		stasis_message_router_add(stasis_router, ast_channel_entered_bridge_type(), handle_bridge_enter_message, NULL);
+		stasis_message_router_add(stasis_router, ast_channel_left_bridge_type(), handle_bridge_leave_message, NULL);
+		stasis_message_router_add(stasis_router, ast_parked_call_type(), handle_parked_call_message, NULL);
+	} else {
+		CDR_DEBUG("All bridge and parking messages will be ignored\n");
+	}
+
 	stasis_message_router_add(stasis_router, cdr_sync_message_type(), handle_cdr_sync_message, NULL);
 
+	if (mod_cfg) {
+		ao2_cleanup(mod_cfg);
+	} else {
+		ast_log(LOG_WARNING, "Unable to obtain CDR configuration during module load?\n");
+	}
+
 	active_cdrs_master = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
 		AST_NUM_CHANNEL_BUCKETS, cdr_master_hash_fn, NULL, cdr_master_cmp_fn);
 	if (!active_cdrs_master) {
diff --git a/main/cel.c b/main/cel.c
index 7e78409bbadca20d43f114276102ca98c0aef47d..942a9afa129ce5ca7a0d424fbc09d7a09f5eb590 100644
--- a/main/cel.c
+++ b/main/cel.c
@@ -102,6 +102,7 @@
 						<enum name="FORWARD"/>
 						<enum name="LINKEDID_END"/>
 						<enum name="LOCAL_OPTIMIZE"/>
+						<enum name="LOCAL_OPTIMIZE_BEGIN"/>
 					</enumlist>
 					</description>
 				</configOption>
@@ -321,6 +322,7 @@ static const char * const cel_event_types[CEL_MAX_EVENT_IDS] = {
 	[AST_CEL_FORWARD]          = "FORWARD",
 	[AST_CEL_LINKEDID_END]     = "LINKEDID_END",
 	[AST_CEL_LOCAL_OPTIMIZE]   = "LOCAL_OPTIMIZE",
+	[AST_CEL_LOCAL_OPTIMIZE_BEGIN]   = "LOCAL_OPTIMIZE_BEGIN",
 };
 
 struct cel_backend {
@@ -1389,9 +1391,11 @@ static void cel_pickup_cb(
 	ast_json_unref(extra);
 }
 
-static void cel_local_cb(
+
+static void cel_local_optimization_cb_helper(
 	void *data, struct stasis_subscription *sub,
-	struct stasis_message *message)
+	struct stasis_message *message,
+	enum ast_cel_event_type event_type)
 {
 	struct ast_multi_channel_blob *obj = stasis_message_data(message);
 	struct ast_channel_snapshot *localone = ast_multi_channel_blob_get_channel(obj, "1");
@@ -1409,10 +1413,27 @@ static void cel_local_cb(
 		return;
 	}
 
-	cel_report_event(localone, AST_CEL_LOCAL_OPTIMIZE, stasis_message_timestamp(message), NULL, extra, NULL);
+	cel_report_event(localone, event_type, stasis_message_timestamp(message), NULL, extra, NULL);
 	ast_json_unref(extra);
 }
 
+static void cel_local_optimization_end_cb(
+	void *data, struct stasis_subscription *sub,
+	struct stasis_message *message)
+{
+	/* The AST_CEL_LOCAL_OPTIMIZE event has always been triggered by the end of optimization.
+	   This can either be used as an indication that the call was locally optimized, or as
+	   the END event in combination with the subsequently added BEGIN event. */
+	cel_local_optimization_cb_helper(data, sub, message, AST_CEL_LOCAL_OPTIMIZE);
+}
+
+static void cel_local_optimization_begin_cb(
+	void *data, struct stasis_subscription *sub,
+	struct stasis_message *message)
+{
+	cel_local_optimization_cb_helper(data, sub, message, AST_CEL_LOCAL_OPTIMIZE_BEGIN);
+}
+
 static void destroy_routes(void)
 {
 	stasis_message_router_unsubscribe_and_join(cel_state_router);
@@ -1555,7 +1576,12 @@ static int create_routes(void)
 
 	ret |= stasis_message_router_add(cel_state_router,
 		ast_local_optimization_end_type(),
-		cel_local_cb,
+		cel_local_optimization_end_cb,
+		NULL);
+
+	ret |= stasis_message_router_add(cel_state_router,
+		ast_local_optimization_begin_type(),
+		cel_local_optimization_begin_cb,
 		NULL);
 
 	if (ret) {
diff --git a/main/channel.c b/main/channel.c
index a10e186264e17ee1ec00b069df5a66bd3699b7e7..eabfe2f0f6b1c0027b2044a0b9ca34065b008f10 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -6134,7 +6134,7 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c
 	}
 
 	/*
-	 * I seems strange to set the CallerID on an outgoing call leg
+	 * It seems strange to set the CallerID on an outgoing call leg
 	 * to whom we are calling, but this function's callers are doing
 	 * various Originate methods.  This call leg goes to the local
 	 * user.  Once the local user answers, the dialplan needs to be
@@ -11191,15 +11191,6 @@ int ast_channel_request_stream_topology_change(struct ast_channel *chan,
 		return -1;
 	}
 
-	if (ast_stream_topology_equal(ast_channel_get_stream_topology(chan), topology)) {
-		ast_debug(2, "%s: Topologies already match. Current: %s  Requested: %s\n",
-				ast_channel_name(chan),
-				ast_str_tmp(256, ast_stream_topology_to_str(ast_channel_get_stream_topology(chan), &STR_TMP)),
-				ast_str_tmp(256, ast_stream_topology_to_str(topology, &STR_TMP)));
-		ast_channel_unlock(chan);
-		return 0;
-	}
-
 	ast_channel_internal_set_stream_topology_change_source(chan, change_source);
 
 	res = ast_channel_tech(chan)->indicate(chan, AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE, topology, sizeof(topology));
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index f2e9d882b34523f90a2dbccdadb93cb1ab10cf1a..543919471aaae96686ceddb1dcf679d076e8ac7d 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -596,6 +596,9 @@ void *ast_channel_tech_pvt(const struct ast_channel *chan)
 void ast_channel_tech_pvt_set(struct ast_channel *chan, void *value)
 {
 	chan->tech_pvt = value;
+	if (value != NULL) {
+		ast_channel_snapshot_invalidate_segment(chan, AST_CHANNEL_SNAPSHOT_INVALIDATE_BASE);
+	}
 }
 void *ast_channel_timingdata(const struct ast_channel *chan)
 {
@@ -1369,7 +1372,8 @@ struct ast_flags *ast_channel_flags(struct ast_channel *chan)
 	return &chan->flags;
 }
 
-static int collect_names_cb(void *obj, void *arg, int flags) {
+static int collect_names_cb(void *obj, void *arg, int flags)
+{
 	struct ast_control_pvt_cause_code *cause_code = obj;
 	struct ast_str **str = arg;
 
diff --git a/main/cli.c b/main/cli.c
index 0c119531a62f848648fc88bd9befe40abe277876..2cb0722ebe875d6c45c94cfe951550905dbcd255 100644
--- a/main/cli.c
+++ b/main/cli.c
@@ -1098,11 +1098,11 @@ static char *handle_showcalls(struct ast_cli_entry *e, int cmd, struct ast_cli_a
 
 static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-#define FORMAT_STRING  "%-20.20s %-20.20s %-7.7s %-30.30s\n"
-#define FORMAT_STRING2 "%-20.20s %-20.20s %-7.7s %-30.30s\n"
+#define FORMAT_STRING  "%-64.64s %-32.32s %-7.7s %-30.30s\n"
+#define FORMAT_STRING2 "%-64.64s %-32.32s %-7.7s %-30.30s\n"
 #define CONCISE_FORMAT_STRING  "%s!%s!%s!%d!%s!%s!%s!%s!%s!%s!%d!%s!%s!%s\n"
-#define VERBOSE_FORMAT_STRING  "%-20.20s %-20.20s %-16.16s %4d %-7.7s %-12.12s %-25.25s %-15.15s %8.8s %-11.11s %-11.11s %-20.20s\n"
-#define VERBOSE_FORMAT_STRING2 "%-20.20s %-20.20s %-16.16s %-4.4s %-7.7s %-12.12s %-25.25s %-15.15s %8.8s %-11.11s %-11.11s %-20.20s\n"
+#define VERBOSE_FORMAT_STRING  "%-80.80s %-24.24s %-24.24s %4d %-7.7s %-12.12s %-25.25s %-15.15s %8.8s %-11.11s %-11.11s %-20.20s\n"
+#define VERBOSE_FORMAT_STRING2 "%-80.80s %-24.24s %-24.24s %-4.4s %-7.7s %-12.12s %-25.25s %-15.15s %8.8s %-11.11s %-11.11s %-20.20s\n"
 
 	struct ao2_container *channels;
 	struct ao2_iterator it_chans;
@@ -2016,50 +2016,27 @@ static char *handle_help(struct ast_cli_entry *e, int cmd, struct ast_cli_args *
 static struct ast_cli_entry cli_cli[] = {
 	AST_CLI_DEFINE(handle_commandmatchesarray, "Returns command matches array"),
 
-	AST_CLI_DEFINE(handle_nodebugchan_deprecated, "Disable debugging on channel(s)"),
-
-	AST_CLI_DEFINE(handle_chanlist, "Display information on channels"),
-
-	AST_CLI_DEFINE(handle_showcalls, "Display information on calls"),
-
-	AST_CLI_DEFINE(handle_showchan, "Display information on a specific channel"),
-
-	AST_CLI_DEFINE(handle_core_set_debug_channel, "Enable/disable debugging on a channel"),
-
 	AST_CLI_DEFINE(handle_debug_category, "Enable/disable debugging categories"),
 
 	AST_CLI_DEFINE(handle_debug, "Set level of debug chattiness"),
 	AST_CLI_DEFINE(handle_trace, "Set level of trace chattiness"),
 	AST_CLI_DEFINE(handle_verbose, "Set level of verbose chattiness"),
 
-	AST_CLI_DEFINE(group_show_channels, "Display active channels with group(s)"),
-
 	AST_CLI_DEFINE(handle_help, "Display help list, or specific help on a command"),
-
 	AST_CLI_DEFINE(handle_logger_mute, "Toggle logging output to a console"),
 
 	AST_CLI_DEFINE(handle_modlist, "List modules and info"),
-
 	AST_CLI_DEFINE(handle_load, "Load a module by name"),
-
 	AST_CLI_DEFINE(handle_reload, "Reload configuration for a module"),
-
 	AST_CLI_DEFINE(handle_core_reload, "Global reload"),
-
 	AST_CLI_DEFINE(handle_unload, "Unload a module by name"),
-
 	AST_CLI_DEFINE(handle_refresh, "Completely unloads and loads a module by name"),
 
 	AST_CLI_DEFINE(handle_showuptime, "Show uptime information"),
 
-	AST_CLI_DEFINE(handle_softhangup, "Request a hangup on a given channel"),
-
 	AST_CLI_DEFINE(handle_cli_reload_permissions, "Reload CLI permissions config"),
-
 	AST_CLI_DEFINE(handle_cli_show_permissions, "Show CLI permissions"),
-
 	AST_CLI_DEFINE(handle_cli_check_permissions, "Try a permissions config for a user"),
-
 	AST_CLI_DEFINE(handle_cli_wait_fullybooted, "Wait for Asterisk to be fully booted"),
 
 #ifdef HAVE_MALLOC_TRIM
@@ -2068,6 +2045,16 @@ static struct ast_cli_entry cli_cli[] = {
 
 };
 
+static struct ast_cli_entry cli_channels_cli[] = {
+	AST_CLI_DEFINE(handle_nodebugchan_deprecated, "Disable debugging on channel(s)"),
+	AST_CLI_DEFINE(handle_chanlist, "Display information on channels"),
+	AST_CLI_DEFINE(handle_showcalls, "Display information on calls"),
+	AST_CLI_DEFINE(handle_showchan, "Display information on a specific channel"),
+	AST_CLI_DEFINE(handle_core_set_debug_channel, "Enable/disable debugging on a channel"),
+	AST_CLI_DEFINE(group_show_channels, "Display active channels with group(s)"),
+	AST_CLI_DEFINE(handle_softhangup, "Request a hangup on a given channel"),
+};
+
 /*!
  * Some regexp characters in cli arguments are reserved and used as separators.
  */
@@ -2239,6 +2226,11 @@ static void cli_shutdown(void)
 	ast_cli_unregister_multiple(cli_cli, ARRAY_LEN(cli_cli));
 }
 
+static void cli_channels_shutdown(void)
+{
+	ast_cli_unregister_multiple(cli_channels_cli, ARRAY_LEN(cli_channels_cli));
+}
+
 /*! \brief initialize the _full_cmd string in * each of the builtins. */
 void ast_builtins_init(void)
 {
@@ -2247,6 +2239,12 @@ void ast_builtins_init(void)
 	ast_register_cleanup(cli_shutdown);
 }
 
+void ast_cli_channels_init(void)
+{
+	ast_cli_register_multiple(cli_channels_cli, ARRAY_LEN(cli_channels_cli));
+	ast_register_cleanup(cli_channels_shutdown);
+}
+
 /*!
  * match a word in the CLI entry.
  * returns -1 on mismatch, 0 on match of an optional word,
diff --git a/main/codec_builtin.c b/main/codec_builtin.c
index 5c460bf1208b533983b3d5e22fc65a39e9d70442..bd69d46be1f08c1490f616e768c06fad5554f5a3 100644
--- a/main/codec_builtin.c
+++ b/main/codec_builtin.c
@@ -703,20 +703,6 @@ static struct ast_codec siren14 = {
 	.get_length = siren14_length,
 };
 
-static struct ast_codec testlaw = {
-	.name = "testlaw",
-	.description = "G.711 test-law",
-	.type = AST_MEDIA_TYPE_AUDIO,
-	.sample_rate = 8000,
-	.minimum_ms = 10,
-	.maximum_ms = 150,
-	.default_ms = 20,
-	.minimum_bytes = 80,
-	.samples_count = ulaw_samples,
-	.get_length = ulaw_length,
-	.smooth = 1,
-};
-
 static int g719_samples(struct ast_frame *frame)
 {
 	return (int) frame->datalen * ((float) 48000 / 8000);
@@ -970,7 +956,6 @@ int ast_codec_builtin_init(void)
 	res |= CODEC_REGISTER_AND_CACHE(g722);
 	res |= CODEC_REGISTER_AND_CACHE(siren7);
 	res |= CODEC_REGISTER_AND_CACHE(siren14);
-	res |= CODEC_REGISTER_AND_CACHE(testlaw);
 	res |= CODEC_REGISTER_AND_CACHE(g719);
 	res |= CODEC_REGISTER_AND_CACHE(opus);
 	res |= CODEC_REGISTER_AND_CACHE(jpeg);
diff --git a/main/config.c b/main/config.c
index 122e7aad480e3005ae755c9c8a98383d8cc94a15..1074407967a9b62eaf49a2b608f0a05224063b40 100644
--- a/main/config.c
+++ b/main/config.c
@@ -646,15 +646,16 @@ struct ast_variable *ast_variable_list_sort(struct ast_variable *start)
 struct ast_variable *ast_variable_list_append_hint(struct ast_variable **head, struct ast_variable *search_hint, struct ast_variable *newvar)
 {
 	struct ast_variable *curr;
+	struct ast_variable *sh = search_hint;
 	ast_assert(head != NULL);
 
 	if (!*head) {
 		*head = newvar;
 	} else {
-		if (search_hint == NULL) {
-			search_hint = *head;
+		if (sh == NULL) {
+			sh = *head;
 		}
-		for (curr = search_hint; curr->next; curr = curr->next);
+		for (curr = sh; curr->next; curr = curr->next);
 		curr->next = newvar;
 	}
 
@@ -722,11 +723,12 @@ struct ast_str *ast_variable_list_join(const struct ast_variable *head, const ch
 	return local_str;
 }
 
-struct ast_variable *ast_variable_list_from_string(const char *input, const char *item_separator,
-	const char *name_value_separator)
+struct ast_variable *ast_variable_list_from_quoted_string(const char *input, const char *item_separator,
+	const char *name_value_separator, const char *quote_str)
 {
 	char item_sep;
 	char nv_sep;
+	char quote;
 	struct ast_variable *new_list = NULL;
 	struct ast_variable *new_var = NULL;
 	char *item_string;
@@ -740,12 +742,19 @@ struct ast_variable *ast_variable_list_from_string(const char *input, const char
 
 	item_sep = ast_strlen_zero(item_separator) ? ',' : item_separator[0];
 	nv_sep = ast_strlen_zero(name_value_separator) ? '=' : name_value_separator[0];
+	quote = ast_strlen_zero(quote_str) ? '"' : quote_str[0];
 	item_string = ast_strip(ast_strdupa(input));
 
-	while ((item = ast_strsep(&item_string, item_sep, AST_STRSEP_ALL))) {
-		item_name = ast_strsep(&item, nv_sep, AST_STRSEP_ALL);
-		item_value = ast_strsep(&item, nv_sep, AST_STRSEP_ALL);
-		new_var = ast_variable_new(item_name, item_value, "");
+	while ((item = ast_strsep_quoted(&item_string, item_sep, quote, AST_STRSEP_ALL))) {
+		item_name = ast_strsep_quoted(&item, nv_sep, quote, AST_STRSEP_ALL);
+		if (!item_name) {
+			ast_variables_destroy(new_list);
+			return NULL;
+		}
+
+		item_value = ast_strsep_quoted(&item, nv_sep, quote, AST_STRSEP_ALL);
+
+		new_var = ast_variable_new(item_name, item_value ?: "", "");
 		if (!new_var) {
 			ast_variables_destroy(new_list);
 			return NULL;
@@ -755,6 +764,12 @@ struct ast_variable *ast_variable_list_from_string(const char *input, const char
 	return new_list;
 }
 
+struct ast_variable *ast_variable_list_from_string(const char *input, const char *item_separator,
+	const char *name_value_separator)
+{
+	return ast_variable_list_from_quoted_string(input, item_separator, name_value_separator, NULL);
+}
+
 const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
 {
 	const char *tmp;
diff --git a/main/config_options.c b/main/config_options.c
index c59a8b58065fa712734de1b7016fc3d6b8467840..d258f607dc84f398b2b121c5a6a60cd40a6521db 100644
--- a/main/config_options.c
+++ b/main/config_options.c
@@ -1099,7 +1099,9 @@ static int xmldoc_update_config_type(const char *module, const char *name, const
 	}
 
 	if (!(results = ast_xmldoc_query("/docs/configInfo[@name='%s']/configFile/configObject[@name='%s']", module, name))) {
-		ast_log(LOG_WARNING, "Cannot update type '%s' in module '%s' because it has no existing documentation!\n", name, module);
+		ast_log(LOG_WARNING, "Cannot update type '%s' in module '%s' because it has no existing documentation!"
+			" If this module was recently built, run 'xmldoc reload' to refresh documentation, then load the module again\n",
+			name, module);
 		return XMLDOC_STRICT ? -1 : 0;
 	}
 
diff --git a/main/conversions.c b/main/conversions.c
index 007e24d758e7634e85fba11c90673d6ed88cd71d..4b58140fd32a19412c919c8994a54aba5851fcbf 100644
--- a/main/conversions.c
+++ b/main/conversions.c
@@ -99,7 +99,7 @@ int ast_str_to_imax(const char *str, intmax_t *res)
 	}
 
 	errno = 0;
-	val = strtoimax(str, &end, 0);
+	val = strtoimax(str, &end, 10);
 
 	/*
 	 * If str equals end then no digits were found. If end is not pointing to
@@ -126,7 +126,7 @@ int ast_str_to_umax(const char *str, uintmax_t *res)
 	}
 
 	errno = 0;
-	val = strtoumax(str, &end, 0);
+	val = strtoumax(str, &end, 10);
 
 	/*
 	 * If str equals end then no digits were found. If end is not pointing to
diff --git a/main/core_unreal.c b/main/core_unreal.c
index a79fca417fd53e6b7e2211e1fd07625f8ca4c2e9..d43272c8742419209507fa8006efc0f14c88a4e3 100644
--- a/main/core_unreal.c
+++ b/main/core_unreal.c
@@ -1168,7 +1168,9 @@ struct ast_channel *ast_unreal_new_channels(struct ast_unreal_pvt *p,
 	struct ast_assigned_ids id1 = {NULL, NULL};
 	struct ast_assigned_ids id2 = {NULL, NULL};
 	int generated_seqno = ast_atomic_fetchadd_int((int *) &name_sequence, +1);
-	struct ast_stream_topology *topology;
+	int i;
+	struct ast_stream_topology *chan_topology;
+	struct ast_stream *stream;
 
 	/* set unique ids for the two channels */
 	if (assignedids && !ast_strlen_zero(assignedids->uniqueid)) {
@@ -1186,14 +1188,27 @@ struct ast_channel *ast_unreal_new_channels(struct ast_unreal_pvt *p,
 		id2.uniqueid = uniqueid2;
 	}
 
-	/* We need to create a topology to place on the first channel, as we can't
+	/* We need to create a topology to place on the second channel, as we can't
 	 * share a single one between both.
 	 */
-	topology = ast_stream_topology_clone(p->reqtopology);
-	if (!topology) {
+	chan_topology = ast_stream_topology_clone(p->reqtopology);
+	if (!chan_topology) {
 		return NULL;
 	}
 
+	for (i = 0; i < ast_stream_topology_get_count(chan_topology); ++i) {
+		stream = ast_stream_topology_get_stream(chan_topology, i);
+		/* We need to make sure that the ;2 channel has the opposite stream topology
+		 * of the first channel if the stream is one-way. I.e. if the first channel
+		 * is recvonly, the second channel has to be sendonly and vice versa.
+		 */
+		if (ast_stream_get_state(stream) == AST_STREAM_STATE_RECVONLY) {
+			ast_stream_set_state(stream, AST_STREAM_STATE_SENDONLY);
+		} else if (ast_stream_get_state(stream) == AST_STREAM_STATE_SENDONLY) {
+			ast_stream_set_state(stream, AST_STREAM_STATE_RECVONLY);
+		}
+	}
+
 	/*
 	 * Allocate two new Asterisk channels
 	 *
@@ -1206,7 +1221,7 @@ struct ast_channel *ast_unreal_new_channels(struct ast_unreal_pvt *p,
 		"%s/%s-%08x;1", tech->type, p->name, (unsigned)generated_seqno);
 	if (!owner) {
 		ast_log(LOG_WARNING, "Unable to allocate owner channel structure\n");
-		ast_stream_topology_free(topology);
+		ast_stream_topology_free(chan_topology);
 		return NULL;
 	}
 
@@ -1221,7 +1236,8 @@ struct ast_channel *ast_unreal_new_channels(struct ast_unreal_pvt *p,
 	ast_channel_nativeformats_set(owner, p->reqcap);
 
 	if (ast_channel_is_multistream(owner)) {
-		ast_channel_set_stream_topology(owner, topology);
+		ast_channel_set_stream_topology(owner, p->reqtopology);
+		p->reqtopology = NULL;
 	}
 
 	/* Determine our read/write format and set it on each channel */
@@ -1279,8 +1295,7 @@ struct ast_channel *ast_unreal_new_channels(struct ast_unreal_pvt *p,
 	ast_channel_nativeformats_set(chan, p->reqcap);
 
 	if (ast_channel_is_multistream(chan)) {
-		ast_channel_set_stream_topology(chan, p->reqtopology);
-		p->reqtopology = NULL;
+		ast_channel_set_stream_topology(chan, chan_topology);
 	}
 
 	/* Format was already determined when setting up owner */
diff --git a/main/datastore.c b/main/datastore.c
index f37ee6c4d4de0cbbc3d506fcbb68701ba1fc0263..d5adfae9c77738494a79d279595d2ecd3b108a6c 100644
--- a/main/datastore.c
+++ b/main/datastore.c
@@ -69,6 +69,10 @@ int ast_datastore_free(struct ast_datastore *datastore)
 {
 	int res = 0;
 
+	if (!datastore) {
+		return 0;
+	}
+
 	/* Using the destroy function (if present) destroy the data */
 	if (datastore->info->destroy != NULL && datastore->data != NULL) {
 		datastore->info->destroy(datastore->data);
diff --git a/main/db.c b/main/db.c
index d4479f4e5a5d652e7f379e8a1aca352ba626320b..a807fdd3bc9aacdb20d58e652b2b47d7fe920f14 100644
--- a/main/db.c
+++ b/main/db.c
@@ -65,6 +65,18 @@
 		<description>
 		</description>
 	</manager>
+	<manager name="DBGetTree" language="en_US">
+		<synopsis>
+			Get DB entries, optionally at a particular family/key
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="Family" required="false" />
+			<parameter name="Key" required="false" />
+		</syntax>
+		<description>
+		</description>
+	</manager>
 	<manager name="DBPut" language="en_US">
 		<synopsis>
 			Put DB entry.
@@ -454,6 +466,38 @@ int ast_db_del(const char *family, const char *key)
 	return res;
 }
 
+int ast_db_del2(const char *family, const char *key)
+{
+	char fullkey[MAX_DB_FIELD];
+	char tmp[1];
+	size_t fullkey_len;
+	int mres, res = 0;
+
+	if (strlen(family) + strlen(key) + 2 > sizeof(fullkey) - 1) {
+		ast_log(LOG_WARNING, "Family and key length must be less than %zu bytes\n", sizeof(fullkey) - 3);
+		return -1;
+	}
+
+	fullkey_len = snprintf(fullkey, sizeof(fullkey), "/%s/%s", family, key);
+
+	ast_mutex_lock(&dblock);
+	if (ast_db_get(family, key, tmp, sizeof(tmp))) {
+		ast_log(LOG_WARNING, "AstDB key %s does not exist\n", fullkey);
+		res = -1;
+	} else if (sqlite3_bind_text(del_stmt, 1, fullkey, fullkey_len, SQLITE_STATIC) != SQLITE_OK) {
+		ast_log(LOG_WARNING, "Couldn't bind key to stmt: %s\n", sqlite3_errmsg(astdb));
+		res = -1;
+	} else if ((mres = sqlite3_step(del_stmt) != SQLITE_DONE)) {
+		ast_log(LOG_WARNING, "AstDB error (%s): %s\n", fullkey, sqlite3_errstr(mres));
+		res = -1;
+	}
+	sqlite3_reset(del_stmt);
+	db_sync();
+	ast_mutex_unlock(&dblock);
+
+	return res;
+}
+
 int ast_db_deltree(const char *family, const char *keytree)
 {
 	sqlite3_stmt *stmt = deltree_stmt;
@@ -475,7 +519,7 @@ int ast_db_deltree(const char *family, const char *keytree)
 
 	ast_mutex_lock(&dblock);
 	if (!ast_strlen_zero(prefix) && (sqlite3_bind_text(stmt, 1, prefix, -1, SQLITE_STATIC) != SQLITE_OK)) {
-		ast_log(LOG_WARNING, "Could bind %s to stmt: %s\n", prefix, sqlite3_errmsg(astdb));
+		ast_log(LOG_WARNING, "Couldn't bind %s to stmt: %s\n", prefix, sqlite3_errmsg(astdb));
 		res = -1;
 	} else if (sqlite3_step(stmt) != SQLITE_DONE) {
 		ast_log(LOG_WARNING, "Couldn't execute stmt: %s\n", sqlite3_errmsg(astdb));
@@ -678,9 +722,9 @@ static char *handle_cli_database_del(struct ast_cli_entry *e, int cmd, struct as
 
 	if (a->argc != 4)
 		return CLI_SHOWUSAGE;
-	res = ast_db_del(a->argv[2], a->argv[3]);
+	res = ast_db_del2(a->argv[2], a->argv[3]);
 	if (res) {
-		ast_cli(a->fd, "Database entry does not exist.\n");
+		ast_cli(a->fd, "Database entry could not be removed.\n");
 	} else {
 		ast_cli(a->fd, "Database entry removed.\n");
 	}
@@ -759,7 +803,7 @@ static char *handle_cli_database_show(struct ast_cli_entry *e, int cmd, struct a
 
 	ast_mutex_lock(&dblock);
 	if (!ast_strlen_zero(prefix) && (sqlite3_bind_text(stmt, 1, prefix, -1, SQLITE_STATIC) != SQLITE_OK)) {
-		ast_log(LOG_WARNING, "Could bind %s to stmt: %s\n", prefix, sqlite3_errmsg(astdb));
+		ast_log(LOG_WARNING, "Couldn't bind %s to stmt: %s\n", prefix, sqlite3_errmsg(astdb));
 		sqlite3_reset(stmt);
 		ast_mutex_unlock(&dblock);
 		return NULL;
@@ -807,7 +851,7 @@ static char *handle_cli_database_showkey(struct ast_cli_entry *e, int cmd, struc
 
 	ast_mutex_lock(&dblock);
 	if (!ast_strlen_zero(a->argv[2]) && (sqlite3_bind_text(showkey_stmt, 1, a->argv[2], -1, SQLITE_STATIC) != SQLITE_OK)) {
-		ast_log(LOG_WARNING, "Could bind %s to stmt: %s\n", a->argv[2], sqlite3_errmsg(astdb));
+		ast_log(LOG_WARNING, "Couldn't bind %s to stmt: %s\n", a->argv[2], sqlite3_errmsg(astdb));
 		sqlite3_reset(showkey_stmt);
 		ast_mutex_unlock(&dblock);
 		return NULL;
@@ -947,6 +991,72 @@ static int manager_dbget(struct mansession *s, const struct message *m)
 	return 0;
 }
 
+static int manager_db_tree_get(struct mansession *s, const struct message *m)
+{
+	char prefix[MAX_DB_FIELD];
+	char idText[256];
+	const char *id = astman_get_header(m,"ActionID");
+	const char *family = astman_get_header(m, "Family");
+	const char *key = astman_get_header(m, "Key");
+	sqlite3_stmt *stmt = gettree_stmt;
+	int count = 0;
+
+	if (!ast_strlen_zero(family) && !ast_strlen_zero(key)) {
+		/* Family and key tree */
+		snprintf(prefix, sizeof(prefix), "/%s/%s", family, key);
+	} else if (!ast_strlen_zero(family)) {
+		/* Family only */
+		snprintf(prefix, sizeof(prefix), "/%s", family);
+	} else {
+		/* Neither */
+		prefix[0] = '\0';
+		stmt = gettree_all_stmt;
+	}
+
+	idText[0] = '\0';
+	if (!ast_strlen_zero(id)) {
+		snprintf(idText, sizeof(idText) ,"ActionID: %s\r\n", id);
+	}
+
+	ast_mutex_lock(&dblock);
+	if (!ast_strlen_zero(prefix) && (sqlite3_bind_text(stmt, 1, prefix, -1, SQLITE_STATIC) != SQLITE_OK)) {
+		ast_log(LOG_WARNING, "Couldn't bind %s to stmt: %s\n", prefix, sqlite3_errmsg(astdb));
+		sqlite3_reset(stmt);
+		ast_mutex_unlock(&dblock);
+		astman_send_error(s, m, "Unable to search database");
+		return 0;
+	}
+
+	astman_send_listack(s, m, "Result will follow", "start");
+
+	while (sqlite3_step(stmt) == SQLITE_ROW) {
+		const char *key_s, *value_s;
+		if (!(key_s = (const char *) sqlite3_column_text(stmt, 0))) {
+			ast_log(LOG_WARNING, "Skipping invalid key!\n");
+			continue;
+		}
+		if (!(value_s = (const char *) sqlite3_column_text(stmt, 1))) {
+			ast_log(LOG_WARNING, "Skipping invalid value!\n");
+			continue;
+		}
+		astman_append(s, "Event: DBGetTreeResponse\r\n"
+			"Key: %s\r\n"
+			"Val: %s\r\n"
+			"%s"
+			"\r\n",
+			key_s, value_s, idText);
+		count++;
+	}
+
+	sqlite3_reset(stmt);
+	ast_mutex_unlock(&dblock);
+
+	astman_send_list_complete_start(s, m, "DBGetTreeComplete", count);
+	astman_send_list_complete_end(s);
+
+	return 0;
+}
+
 static int manager_dbdel(struct mansession *s, const struct message *m)
 {
 	const char *family = astman_get_header(m, "Family");
@@ -963,9 +1073,9 @@ static int manager_dbdel(struct mansession *s, const struct message *m)
 		return 0;
 	}
 
-	res = ast_db_del(family, key);
+	res = ast_db_del2(family, key);
 	if (res)
-		astman_send_error(s, m, "Database entry not found");
+		astman_send_error(s, m, "Database entry could not be deleted");
 	else
 		astman_send_ack(s, m, "Key deleted successfully");
 
@@ -1059,6 +1169,7 @@ static void astdb_atexit(void)
 {
 	ast_cli_unregister_multiple(cli_database, ARRAY_LEN(cli_database));
 	ast_manager_unregister("DBGet");
+	ast_manager_unregister("DBGetTree");
 	ast_manager_unregister("DBPut");
 	ast_manager_unregister("DBDel");
 	ast_manager_unregister("DBDelTree");
@@ -1094,6 +1205,7 @@ int astdb_init(void)
 	ast_register_atexit(astdb_atexit);
 	ast_cli_register_multiple(cli_database, ARRAY_LEN(cli_database));
 	ast_manager_register_xml_core("DBGet", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_dbget);
+	ast_manager_register_xml_core("DBGetTree", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, manager_db_tree_get);
 	ast_manager_register_xml_core("DBPut", EVENT_FLAG_SYSTEM, manager_dbput);
 	ast_manager_register_xml_core("DBDel", EVENT_FLAG_SYSTEM, manager_dbdel);
 	ast_manager_register_xml_core("DBDelTree", EVENT_FLAG_SYSTEM, manager_dbdeltree);
diff --git a/main/dial.c b/main/dial.c
index c40b7fbb6d37883f19e3d5420c166d9ecf147cd7..3906bd06b966325ca7c984b2a9aa2dc7b001a964 100644
--- a/main/dial.c
+++ b/main/dial.c
@@ -166,14 +166,12 @@ static int predial_disable(void *data)
 static void answer_exec_run(struct ast_dial *dial, struct ast_dial_channel *dial_channel, char *app, char *args)
 {
 	struct ast_channel *chan = dial_channel->owner;
-	struct ast_app *ast_app = pbx_findapp(app);
 
-	/* If the application was not found, return immediately */
-	if (!ast_app)
+	/* Execute the application, if available */
+	if (ast_pbx_exec_application(chan, app, args)) {
+		/* If the application was not found, return immediately */
 		return;
-
-	/* All is well... execute the application */
-	pbx_exec(chan, ast_app, args);
+	}
 
 	/* If another thread is not taking over hang up the channel */
 	ast_mutex_lock(&dial->lock);
@@ -1045,8 +1043,17 @@ enum ast_dial_result ast_dial_join(struct ast_dial *dial)
 			ast_channel_unlock(chan);
 		}
 	} else {
+		struct ast_dial_channel *channel = NULL;
+
 		/* Now we signal it with SIGURG so it will break out of it's waitfor */
 		pthread_kill(thread, SIGURG);
+
+		/* pthread_kill may not be enough, if outgoing channel has already got an answer (no more in waitfor) but is not yet running an application. Force soft hangup. */
+		AST_LIST_TRAVERSE(&dial->channels, channel, list) {
+			if (channel->owner) {
+				ast_softhangup(channel->owner, AST_SOFTHANGUP_EXPLICIT);
+			}
+		}
 	}
 	AST_LIST_UNLOCK(&dial->channels);
 
diff --git a/main/features.c b/main/features.c
index b67bf387c00521285ffd29ab4cd860de170f5bd7..39d8aabde5c781ebb9e91161796d966a6ad39b15 100644
--- a/main/features.c
+++ b/main/features.c
@@ -164,6 +164,12 @@
 							</variable>
 						</variablelist>
 					</option>
+					<option name="n">
+						<para>Do not answer the channel automatically before bridging.</para>
+						<para>Additionally, to prevent a bridged channel (the target of the Bridge application)
+						from answering, the <literal>BRIDGE_NOANSWER</literal> variable can be set to inhibit
+						answering.</para>
+					</option>
 					<option name="S(x)">
 						<para>Hang up the call after <replaceable>x</replaceable> seconds *after* the called party has answered the call.</para>
 					</option>
@@ -504,12 +510,7 @@ static void bridge_check_monitor(struct ast_channel *chan, struct ast_channel *p
 		ast_channel_unlock(peer);
 	}
 	if (monitor_chan) {
-		struct ast_app *monitor_app;
-
-		monitor_app = pbx_findapp("Monitor");
-		if (monitor_app) {
-			pbx_exec(monitor_chan, monitor_app, monitor_args);
-		}
+		ast_pbx_exec_application(monitor_chan, "Monitor", monitor_args);
 	}
 }
 
@@ -530,7 +531,7 @@ static void bridge_failed_peer_goto(struct ast_channel *chan, struct ast_channel
 }
 
 static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config,
-		struct ast_bridge_features *chan_features, struct ast_bridge_features *peer_features)
+		struct ast_bridge_features *chan_features, struct ast_bridge_features *peer_features, int noanswer)
 {
 	int res;
 	SCOPE_TRACE(1, "%s Peer: %s\n", ast_channel_name(chan), ast_channel_name(peer));
@@ -558,7 +559,10 @@ static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer,
 
 	res = 0;
 
-	if (ast_channel_state(chan) != AST_STATE_UP) {
+	if (noanswer) {
+		ast_debug(1, "Skipping answer on %s due to no answer directive\n", ast_channel_name(chan));
+	} else if (ast_channel_state(chan) != AST_STATE_UP) {
+		ast_debug(1, "Answering channel for bridge: %s\n", ast_channel_name(chan));
 		res = ast_raw_answer_with_stream_topology(chan, config->answer_topology);
 		if (res != 0) {
 			return -1;
@@ -632,6 +636,8 @@ int ast_bridge_call_with_flags(struct ast_channel *chan, struct ast_channel *pee
 	struct ast_bridge *bridge;
 	struct ast_bridge_features chan_features;
 	struct ast_bridge_features *peer_features;
+	const char *value;
+	int noanswer;
 	SCOPE_TRACE(1, "%s Peer: %s\n", ast_channel_name(chan), ast_channel_name(peer));
 
 	/* Setup features. */
@@ -644,7 +650,12 @@ int ast_bridge_call_with_flags(struct ast_channel *chan, struct ast_channel *pee
 		return -1;
 	}
 
-	if (pre_bridge_setup(chan, peer, config, &chan_features, peer_features)) {
+	ast_channel_lock(chan);
+	value = pbx_builtin_getvar_helper(chan, "BRIDGE_NOANSWER");
+	noanswer = !ast_strlen_zero(value) ? 1 : 0;
+	ast_channel_unlock(chan);
+
+	if (pre_bridge_setup(chan, peer, config, &chan_features, peer_features, noanswer)) {
 		ast_bridge_features_destroy(peer_features);
 		ast_bridge_features_cleanup(&chan_features);
 		bridge_failed_peer_goto(chan, peer);
@@ -853,6 +864,7 @@ enum {
 	OPT_CALLER_PARK = (1 << 10),
 	OPT_CALLEE_KILL = (1 << 11),
 	OPT_CALLEE_GO_ON = (1 << 12),
+	OPT_NOANSWER = (1 << 13),
 };
 
 enum {
@@ -871,6 +883,7 @@ AST_APP_OPTIONS(bridge_exec_options, BEGIN_OPTIONS
 	AST_APP_OPTION('k', OPT_CALLEE_PARK),
 	AST_APP_OPTION('K', OPT_CALLER_PARK),
 	AST_APP_OPTION_ARG('L', OPT_DURATION_LIMIT, OPT_ARG_DURATION_LIMIT),
+	AST_APP_OPTION('n', OPT_NOANSWER),
 	AST_APP_OPTION_ARG('S', OPT_DURATION_STOP, OPT_ARG_DURATION_STOP),
 	AST_APP_OPTION('t', OPT_CALLEE_TRANSFER),
 	AST_APP_OPTION('T', OPT_CALLER_TRANSFER),
@@ -1017,6 +1030,7 @@ static int bridge_exec(struct ast_channel *chan, const char *data)
 	struct ast_bridge_features *peer_features;
 	struct ast_bridge *bridge;
 	struct ast_features_xfer_config *xfer_cfg;
+	int noanswer;
 
 	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(dest_chan);
@@ -1071,6 +1085,7 @@ static int bridge_exec(struct ast_channel *chan, const char *data)
 		ast_set_flag(&(bconfig.features_callee), AST_FEATURE_PARKCALL);
 	if (ast_test_flag(&opts, OPT_CALLER_PARK))
 		ast_set_flag(&(bconfig.features_caller), AST_FEATURE_PARKCALL);
+	noanswer = ast_test_flag(&opts, OPT_NOANSWER);
 
 	/* Setup after bridge goto location. */
 	if (ast_test_flag(&opts, OPT_CALLEE_GO_ON)) {
@@ -1101,7 +1116,7 @@ static int bridge_exec(struct ast_channel *chan, const char *data)
 		goto done;
 	}
 
-	if (pre_bridge_setup(chan, current_dest_chan, &bconfig, &chan_features, peer_features)) {
+	if (pre_bridge_setup(chan, current_dest_chan, &bconfig, &chan_features, peer_features, noanswer)) {
 		ast_bridge_features_destroy(peer_features);
 		ast_bridge_features_cleanup(&chan_features);
 		goto done;
diff --git a/main/features_config.c b/main/features_config.c
index 50ca69b4a43ae4a74336aba477c7d555c0b53cfe..97ace55f41e74a6d55a36ac3e2db078eac8abada 100644
--- a/main/features_config.c
+++ b/main/features_config.c
@@ -143,6 +143,9 @@
 				<configOption name="transferinvalidsound" default="privacy-incorrect">
 					<synopsis>Sound that is played when an incorrect extension is dialed and the transferer has no attempts remaining.</synopsis>
 				</configOption>
+				<configOption name="transferannouncesound" default="pbx-transfer">
+					<synopsis>Sound that is played to the transferer when a transfer is initiated. If empty, no sound will be played.</synopsis>
+				</configOption>
 			</configObject>
 			<configObject name="featuremap">
 				<synopsis>DTMF options that can be triggered during bridged calls</synopsis>
@@ -184,30 +187,40 @@
 					</description>
 				</configOption>
 				<configOption name="automon">
-					<synopsis>DTMF sequence to start or stop monitoring a call</synopsis>
+					<synopsis>DTMF sequence to start or stop Monitor on a call</synopsis>
 					<description>
 						<para>This will cause the channel that pressed the DTMF sequence
 						to be monitored by the <literal>Monitor</literal> application. The
 						format for the recording is determined by the <replaceable>TOUCH_MONITOR_FORMAT</replaceable>
 						channel variable. If this variable is not specified, then <literal>wav</literal> is the
 						default. The filename is constructed in the following manner:</para>
-
-						<para>    prefix-timestamp-filename</para>
-
+						<para>    prefix-timestamp-suffix.fmt</para>
 						<para>where prefix is either the value of the <replaceable>TOUCH_MONITOR_PREFIX</replaceable>
 						channel variable or <literal>auto</literal> if the variable is not set. The timestamp
-						is a UNIX timestamp. The filename is either the value of the <replaceable>TOUCH_MONITOR</replaceable>
+						is a UNIX timestamp. The suffix is either the value of the <replaceable>TOUCH_MONITOR</replaceable>
 						channel variable or the callerID of the channels if the variable is not set.</para>
+						<para>To play a periodic beep while this call is being recorded, set the
+						<replaceable>TOUCH_MONITOR_BEEP</replaceable> to the interval in seconds. The interval will default
+						to 15 seconds if invalid.  The minimum interval is 5 seconds.</para>
 					</description>
+					<see-also><ref type="configOption">automixmon</ref></see-also>
 				</configOption>
 				<configOption name="automixmon">
-					<synopsis>DTMF sequence to start or stop mixmonitoring a call </synopsis>
+					<synopsis>DTMF sequence to start or stop MixMonitor on a call</synopsis>
 					<description>
-						<para>Operation of the automixmon is similar to the <literal> automon </literal>
-						feature, with the following exceptions:
-							<replaceable>TOUCH_MIXMONITOR</replaceable> is used in place of <replaceable>TOUCH_MONITOR</replaceable>
-							<replaceable>TOUCH_MIXMONITOR_FORMAT</replaceable> is used in place of <replaceable>TOUCH_MIXMONITOR</replaceable>
-							There is no equivalent for <replaceable>TOUCH_MONITOR_PREFIX</replaceable>. <literal>"auto"</literal> is always how the filename begins.</para>
+						<para>This will cause the channel that pressed the DTMF sequence
+						to be monitored by the <literal>MixMonitor</literal> application. The
+						format for the recording is determined by the <replaceable>TOUCH_MIXMONITOR_FORMAT</replaceable>
+						channel variable. If this variable is not specified, then <literal>wav</literal> is the
+						default. The filename is constructed in the following manner:</para>
+						<para>    prefix-timestamp-suffix.fmt</para>
+						<para>where prefix is either the value of the <replaceable>TOUCH_MIXMONITOR_PREFIX</replaceable>
+						channel variable or <literal>auto</literal> if the variable is not set. The timestamp
+						is a UNIX timestamp. The suffix is either the value of the <replaceable>TOUCH_MIXMONITOR</replaceable>
+						channel variable or the callerID of the channels if the variable is not set.</para>
+						<para>To play a periodic beep while this call is being recorded, set the
+						<replaceable>TOUCH_MIXMONITOR_BEEP</replaceable> to the interval in seconds. The interval will default
+						to 15 seconds if invalid.  The minimum interval is 5 seconds.</para>
 					</description>
 					<see-also><ref type="configOption">automon</ref></see-also>
 				</configOption>
@@ -320,6 +333,7 @@
 					<enum name="transferdialattempts"><para><xi:include xpointer="xpointer(/docs/configInfo[@name='features']/configFile[@name='features.conf']/configObject[@name='globals']/configOption[@name='transferdialattempts']/synopsis/text())" /></para></enum>
 					<enum name="transferretrysound"><para><xi:include xpointer="xpointer(/docs/configInfo[@name='features']/configFile[@name='features.conf']/configObject[@name='globals']/configOption[@name='transferretrysound']/synopsis/text())" /></para></enum>
 					<enum name="transferinvalidsound"><para><xi:include xpointer="xpointer(/docs/configInfo[@name='features']/configFile[@name='features.conf']/configObject[@name='globals']/configOption[@name='transferinvalidsound']/synopsis/text())" /></para></enum>
+					<enum name="transferannouncesound"><para><xi:include xpointer="xpointer(/docs/configInfo[@name='features']/configFile[@name='features.conf']/configObject[@name='globals']/configOption[@name='transferannouncesound']/synopsis/text())" /></para></enum>
 				</enumlist>
 			</parameter>
 		</syntax>
@@ -383,6 +397,7 @@
 #define DEFAULT_TRANSFER_DIAL_ATTEMPTS              3
 #define DEFAULT_TRANSFER_RETRY_SOUND                "pbx-invalid"
 #define DEFAULT_TRANSFER_INVALID_SOUND              "privacy-incorrect"
+#define DEFAULT_TRANSFER_ANNOUNCE_SOUND             "pbx-transfer"
 
 /*! Default pickup options */
 #define DEFAULT_PICKUPEXTEN                         "*8"
@@ -906,6 +921,8 @@ static int xfer_set(struct ast_features_xfer_config *xfer, const char *name,
 		ast_string_field_set(xfer, transferretrysound, value);
 	} else if (!strcasecmp(name, "transferinvalidsound")) {
 		ast_string_field_set(xfer, transferinvalidsound, value);
+	} else if (!strcasecmp(name, "transferannouncesound")) {
+		ast_string_field_set(xfer, transferannouncesound, value);
 	} else {
 		/* Unrecognized option */
 		res = -1;
@@ -1797,6 +1814,8 @@ static int load_config(void)
 			DEFAULT_TRANSFER_RETRY_SOUND, xfer_handler, 0);
 	aco_option_register_custom(&cfg_info, "transferinvalidsound", ACO_EXACT, global_options,
 			DEFAULT_TRANSFER_INVALID_SOUND, xfer_handler, 0);
+	aco_option_register_custom(&cfg_info, "transferannouncesound", ACO_EXACT, global_options,
+			DEFAULT_TRANSFER_ANNOUNCE_SOUND, xfer_handler, 0);
 
 	aco_option_register_custom(&cfg_info, "pickupexten", ACO_EXACT, global_options,
 			DEFAULT_PICKUPEXTEN, pickup_handler, 0);
diff --git a/main/file.c b/main/file.c
index a6cd3008fa074504fadd2c9b98ff55740e4ec1ce..d7c75430bbbe7430bc2df6e5e91b8f2cda0b19ce 100644
--- a/main/file.c
+++ b/main/file.c
@@ -1097,6 +1097,12 @@ int ast_stream_fastforward(struct ast_filestream *fs, off_t ms)
 
 int ast_stream_rewind(struct ast_filestream *fs, off_t ms)
 {
+	off_t offset = ast_tellstream(fs);
+	if (ms * DEFAULT_SAMPLES_PER_MS > offset) {
+		/* Don't even bother asking the file format to seek to a negative offset... */
+		ast_debug(1, "Restarting, rather than seeking to negative offset %ld\n", (long) (offset - (ms * DEFAULT_SAMPLES_PER_MS)));
+		return ast_seekstream(fs, 0, SEEK_SET);
+	}
 	return ast_seekstream(fs, -ms * DEFAULT_SAMPLES_PER_MS, SEEK_CUR);
 }
 
@@ -1738,6 +1744,7 @@ static int waitstream_core(struct ast_channel *c,
 				case AST_CONTROL_UPDATE_RTP_PEER:
 				case AST_CONTROL_PVT_CAUSE_CODE:
 				case AST_CONTROL_FLASH:
+				case AST_CONTROL_WINK:
 				case -1:
 					/* Unimportant */
 					break;
@@ -1855,6 +1862,10 @@ int ast_stream_and_wait(struct ast_channel *chan, const char *file, const char *
 			res = ast_waitstream(chan, digits);
 		}
 	}
+	if (res == -1) {
+		ast_stopstream(chan);
+	}
+
 	return res;
 }
 
diff --git a/main/format_cache.c b/main/format_cache.c
index 3ce8ee02bc88825c8431e04cb610870a1f153b6a..f1a7fdac07aced908ef281932360c9f808af46c6 100644
--- a/main/format_cache.c
+++ b/main/format_cache.c
@@ -90,11 +90,6 @@ struct ast_format *ast_format_ulaw;
  */
 struct ast_format *ast_format_alaw;
 
-/*!
- * \brief Built-in cached testlaw format.
- */
-struct ast_format *ast_format_testlaw;
-
 /*!
  * \brief Built-in cached gsm format.
  */
@@ -343,7 +338,6 @@ static void format_cache_shutdown(void)
 	ao2_replace(ast_format_g722, NULL);
 	ao2_replace(ast_format_siren7, NULL);
 	ao2_replace(ast_format_siren14, NULL);
-	ao2_replace(ast_format_testlaw, NULL);
 	ao2_replace(ast_format_g719, NULL);
 	ao2_replace(ast_format_opus, NULL);
 	ao2_replace(ast_format_codec2, NULL);
@@ -434,8 +428,6 @@ static void set_cached_format(const char *name, struct ast_format *format)
 		ao2_replace(ast_format_siren7, format);
 	} else if (!strcmp(name, "siren14")) {
 		ao2_replace(ast_format_siren14, format);
-	} else if (!strcmp(name, "testlaw")) {
-		ao2_replace(ast_format_testlaw, format);
 	} else if (!strcmp(name, "g719")) {
 		ao2_replace(ast_format_g719, format);
 	} else if (!strcmp(name, "opus")) {
diff --git a/main/format_compatibility.c b/main/format_compatibility.c
index 706e1b25819d2e21818211ee61776e4e2dd84361..7f2faa735c03a7f635b35be878bf65743f6232b4 100644
--- a/main/format_compatibility.c
+++ b/main/format_compatibility.c
@@ -76,8 +76,6 @@ uint64_t ast_format_compatibility_format2bitfield(const struct ast_format *forma
 		return AST_FORMAT_SPEEX16;
 	} else if (ast_format_cmp(format, ast_format_opus) == AST_FORMAT_CMP_EQUAL) {
 		return AST_FORMAT_OPUS;
-	} else if (ast_format_cmp(format, ast_format_testlaw) == AST_FORMAT_CMP_EQUAL) {
-		return AST_FORMAT_TESTLAW;
 	} else if (ast_format_cmp(format, ast_format_h261) == AST_FORMAT_CMP_EQUAL) {
 		return AST_FORMAT_H261;
 	} else if (ast_format_cmp(format, ast_format_h263) == AST_FORMAT_CMP_EQUAL) {
@@ -143,8 +141,6 @@ uint64_t ast_format_compatibility_codec2bitfield(const struct ast_codec *codec)
 		return AST_FORMAT_SPEEX16;
 	} else if (codec->id == ast_format_get_codec_id(ast_format_opus)) {
 		return AST_FORMAT_OPUS;
-	} else if (codec->id == ast_format_get_codec_id(ast_format_testlaw)) {
-		return AST_FORMAT_TESTLAW;
 	} else if (codec->id == ast_format_get_codec_id(ast_format_h261)) {
 		return AST_FORMAT_H261;
 	} else if (codec->id == ast_format_get_codec_id(ast_format_h263)) {
@@ -230,9 +226,6 @@ struct ast_format *ast_format_compatibility_bitfield2format(uint64_t bitfield)
 	/*! Opus audio (8kHz, 16kHz, 24kHz, 48Khz) */
 	case AST_FORMAT_OPUS:
 		return ast_format_opus;
-	/*! Raw mu-law data (G.711) */
-	case AST_FORMAT_TESTLAW:
-		return ast_format_testlaw;
 
 	/*! H.261 Video */
 	case AST_FORMAT_H261:
diff --git a/main/http.c b/main/http.c
index 7f1ffe0c570eb5c38ff9e4f892956be6dacc8b8f..80c00c52a103984dceb9ae48330229c133e23b31 100644
--- a/main/http.c
+++ b/main/http.c
@@ -396,10 +396,12 @@ static int httpstatus_callback(struct ast_tcptls_session_instance *ser,
 
 	ast_str_append(&out, 0, "<tr><td><i>Server</i></td><td><b>%s</b></td></tr>\r\n", http_server_name);
 	ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
-	ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
-		       ast_sockaddr_stringify_addr(&global_http_server->args.old_address));
-	ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%s</b></td></tr>\r\n",
-		       ast_sockaddr_stringify_port(&global_http_server->args.old_address));
+	if (global_http_server) {
+		ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
+			       ast_sockaddr_stringify_addr(&global_http_server->args.old_address));
+		ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%s</b></td></tr>\r\n",
+			       ast_sockaddr_stringify_port(&global_http_server->args.old_address));
+	}
 	if (http_tls_cfg.enabled) {
 		ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%s</b></td></tr>\r\n",
 			       ast_sockaddr_stringify_port(&https_desc.old_address));
diff --git a/main/iostream.c b/main/iostream.c
index d060b6d6d445cda68d6ce6968ad4ce8b4b15b9fe..7727983423bcb29d2f465f0232bb2b84b018092d 100644
--- a/main/iostream.c
+++ b/main/iostream.c
@@ -553,7 +553,7 @@ int ast_iostream_close(struct ast_iostream *stream)
 					ERR_error_string(sslerr, err), ssl_error_to_string(sslerr, res));
 			}
 
-#if !defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+#if !(defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x2070000L)) && (OPENSSL_VERSION_NUMBER >= 0x10100000L)
 			if (!SSL_is_server(stream->ssl)) {
 #else
 			if (!stream->ssl->server) {
diff --git a/main/json.c b/main/json.c
index 616b12e67fd790b966ed6ae53f347b288b772d74..afb653a229a3217d618a2a0eb9971b8c18ae0dd2 100644
--- a/main/json.c
+++ b/main/json.c
@@ -456,8 +456,19 @@ int ast_json_object_iter_set(struct ast_json *object, struct ast_json_iter *iter
  */
 static size_t dump_flags(enum ast_json_encoding_format format)
 {
-	return format == AST_JSON_PRETTY ?
-		JSON_INDENT(2) | JSON_PRESERVE_ORDER : JSON_COMPACT;
+	size_t jansson_dump_flags;
+
+	if (format & AST_JSON_PRETTY) {
+		jansson_dump_flags = JSON_INDENT(2);
+	} else {
+		jansson_dump_flags = JSON_COMPACT;
+	}
+
+	if (format & AST_JSON_SORTED) {
+		jansson_dump_flags |= JSON_SORT_KEYS;
+	}
+
+	return jansson_dump_flags;
 }
 
 char *ast_json_dump_string_format(struct ast_json *root, enum ast_json_encoding_format format)
diff --git a/main/loader.c b/main/loader.c
index aafcd3ba1f4fcfe198699bcda417aafafae2c555..1c06d6e648f940a7ef1490ae6738822a37f209e1 100644
--- a/main/loader.c
+++ b/main/loader.c
@@ -885,17 +885,7 @@ static int printdigest(const unsigned char *d)
 	return 0;
 }
 
-static int key_matches(const unsigned char *key1, const unsigned char *key2)
-{
-	int x;
-
-	for (x = 0; x < 16; x++) {
-		if (key1[x] != key2[x])
-			return 0;
-	}
-
-	return 1;
-}
+#define key_matches(a, b) (memcmp((a), (b), 16) == 0)
 
 static int verify_key(const unsigned char *key)
 {
@@ -1241,8 +1231,17 @@ int ast_unload_resource(const char *resource_name, enum ast_module_unload_mode f
 	}
 
 	if (!mod->flags.running || mod->flags.declined) {
-		ast_log(LOG_WARNING, "Unload failed, '%s' is not loaded.\n", resource_name);
-		error = 1;
+		/* If the user asks to unload a module that didn't load, obey.
+		 * Otherwise, we never dlclose() modules that fail to load,
+		 * which means if the module (shared object) is updated,
+		 * we can't load the updated module since we never dlclose()'d it.
+		 * Accordingly, obey the unload request so we can load the module
+		 * from scratch next time.
+		 */
+		ast_log(LOG_NOTICE, "Unloading module '%s' that previously declined to load\n", resource_name);
+		error = 0;
+		res = 0;
+		goto exit; /* Skip all the intervening !error checks, only the last one is relevant. */
 	}
 
 	if (!error && (mod->usecount > 0)) {
@@ -1284,6 +1283,7 @@ int ast_unload_resource(const char *resource_name, enum ast_module_unload_mode f
 	if (!error)
 		mod->flags.running = mod->flags.declined = 0;
 
+exit:
 	AST_DLLIST_UNLOCK(&module_list);
 
 	if (!error) {
@@ -1382,6 +1382,11 @@ char *ast_module_helper(const char *line, const char *word, int pos, int state,
 		return NULL;
 	}
 
+	/* Tab completion can't be used during startup, or CLI and loader will deadlock. */
+	if (!ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED)) {
+		return NULL;
+	}
+
 	if (type == AST_MODULE_HELPER_LOAD) {
 		module_load_helper(word);
 
@@ -1818,7 +1823,18 @@ prestart_error:
 
 int ast_load_resource(const char *resource_name)
 {
+	struct ast_module *mod;
 	int res;
+
+	/* If we're trying to load a module that previously declined to load,
+	 * transparently unload it first so we dlclose, then dlopen it afresh.
+	 * Otherwise, we won't actually load a (potentially) updated module. */
+	mod = find_resource(resource_name, 0);
+	if (mod && mod->flags.declined) {
+		ast_debug(1, "Module %s previously declined to load, unloading it first before loading again\n", resource_name);
+		ast_unload_resource(resource_name, 0);
+	}
+
 	AST_DLLIST_LOCK(&module_list);
 	res = load_resource(resource_name, 0, NULL, 0, 0);
 	if (!res) {
@@ -2517,9 +2533,9 @@ done:
 	usElapsed = ast_tvdiff_us(end_time, start_time);
 
 #ifdef AST_XML_DOCS
-	ast_debug(1, "Loader time with AST_XML_DOCS: %ld.%06ld\n", usElapsed / 1000000, usElapsed % 1000000);
+	ast_debug(1, "Loader time with AST_XML_DOCS: %" PRId64 ".%06" PRId64 "\n", usElapsed / 1000000, usElapsed % 1000000);
 #else
-	ast_debug(1, "Loader time without AST_XML_DOCS: %ld.%06ld\n", usElapsed / 1000000, usElapsed % 1000000);
+	ast_debug(1, "Loader time without AST_XML_DOCS: %" PRId64 ".%06" PRId64 "\n", usElapsed / 1000000, usElapsed % 1000000);
 #endif
 
 	return res;
diff --git a/main/lock.c b/main/lock.c
index fcf0839f405a3ef76ff492586cc53856679b38eb..8d09f00520cd53c2ed79cca6a5f343691a03df76 100644
--- a/main/lock.c
+++ b/main/lock.c
@@ -38,6 +38,7 @@ static void __attribute__((constructor)) __mtx_init(void)
 
 #include "asterisk/utils.h"
 #include "asterisk/lock.h"
+#include "asterisk/manager.h"
 
 /* Allow direct use of pthread_mutex_* / pthread_cond_* */
 #undef pthread_mutex_init
@@ -311,6 +312,26 @@ int __ast_pthread_mutex_lock(const char *filename, int lineno, const char *func,
 						ast_reentrancy_unlock(lt);
 					}
 					reported_wait = wait_time;
+					if ((int) wait_time < 10) { /* Only emit an event when a deadlock starts, not every 5 seconds */
+						/*** DOCUMENTATION
+							<managerEvent language="en_US" name="DeadlockStart">
+								<managerEventInstance class="EVENT_FLAG_SYSTEM">
+									<synopsis>Raised when a probable deadlock has started.
+									Delivery of this event is attempted but not guaranteed,
+                                                                        and could fail for example if the manager itself is deadlocked.
+                                                                        </synopsis>
+										<syntax>
+											<parameter name="Mutex">
+												<para>The mutex involved in the deadlock.</para>
+											</parameter>
+										</syntax>
+								</managerEventInstance>
+							</managerEvent>
+						***/
+						manager_event(EVENT_FLAG_SYSTEM, "DeadlockStart",
+							"Mutex: %s\r\n",
+							mutex_name);
+					}
 				}
 				usleep(200);
 			}
diff --git a/main/logger.c b/main/logger.c
index 41dbe6751630c43827520e359e53bcfab7df72c6..110483a4f7505c59a3078e256f6301bba4a5480f 100644
--- a/main/logger.c
+++ b/main/logger.c
@@ -97,6 +97,11 @@ static int logger_queue_limit = 1000;
 static int logger_messages_discarded;
 static unsigned int high_water_alert;
 
+/* On some platforms, like those with MUSL as the runtime, BUFSIZ is
+ * unreasonably small (1024). Use a larger value in those environments.
+ */
+#define LOGMSG_SIZE		MAX(BUFSIZ, 8192)
+
 static enum rotatestrategy {
 	NONE = 0,                /* Do not rotate log files at all, instead rely on external mechanisms */
 	SEQUENTIAL = 1 << 0,     /* Original method - create a new file, in order */
diff --git a/main/manager.c b/main/manager.c
index 76a6611a241fd2fb999e88cb86a72fb34d317c08..c5a48fd4cb16af32d611ad33bd2eac302ac9c0db 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -633,6 +633,12 @@
 		</see-also>
 	</manager>
 	<manager name="CancelAtxfer" language="en_US">
+		<since>
+			<version>13.18.0</version>
+			<version>14.7.0</version>
+			<version>15.1.0</version>
+			<version>16.0.0</version>
+		</since>
 		<synopsis>
 			Cancel an attended transfer.
 		</synopsis>
@@ -1103,7 +1109,8 @@
 		</syntax>
 		<description>
 			<para>Checks if Asterisk module is loaded. Will return Success/Failure.
-			For success returns, the module revision number is included.</para>
+			An empty Version header is also returned (which doesn't contain
+			the module revision number).</para>
 		</description>
 		<see-also>
 			<ref type="manager">ModuleLoad</ref>
@@ -1115,8 +1122,9 @@
 		</synopsis>
 		<syntax>
 			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
-			<parameter name="Channel" required="true">
-				<para>Channel name to generate the AOC message on.</para>
+			<parameter name="Channel">
+				<para>Channel name to generate the AOC message on.
+				This value is required unless ChannelPrefix is given.</para>
 			</parameter>
 			<parameter name="ChannelPrefix">
 				<para>Partial channel prefix.  By using this option one can match the beginning part
@@ -1126,14 +1134,15 @@
 				the first matched channel has the message sent on it. </para>
 			</parameter>
 			<parameter name="MsgType" required="true">
-				<para>Defines what type of AOC message to create, AOC-D or AOC-E</para>
+				<para>Defines what type of AOC message to create, AOC-S, AOC-D or AOC-E</para>
 				<enumlist>
+					<enum name="S" />
 					<enum name="D" />
 					<enum name="E" />
 				</enumlist>
 			</parameter>
-			<parameter name="ChargeType" required="true">
-				<para>Defines what kind of charge this message represents.</para>
+			<parameter name="ChargeType">
+				<para>Defines what kind of charge this message represents for AOC-D and AOC-E.</para>
 				<enumlist>
 					<enum name="NA" />
 					<enum name="FREE" />
@@ -1161,11 +1170,13 @@
 				<para>Specifies the currency's name.  Note that this value is truncated after 10 characters.</para>
 			</parameter>
 			<parameter name="CurrencyAmount">
-				<para>Specifies the charge unit amount as a positive integer.  This value is required
-				when ChargeType==Currency.</para>
+				<para>Specifies the charge unit amount as a positive integer.
+				This value is required when ChargeType==Currency (AOC-D or AOC-E) or
+				RateType==Duration/Flat/Volume (AOC-S).</para>
 			</parameter>
 			<parameter name="CurrencyMultiplier">
-				<para>Specifies the currency multiplier.  This value is required when ChargeType==Currency.</para>
+				<para>Specifies the currency multiplier.
+				This value is required when CurrencyAmount is given.</para>
 				<enumlist>
 					<enum name="OneThousandth" />
 					<enum name="OneHundredth" />
@@ -1210,11 +1221,102 @@
 				The value is bits 7 through 1 of the Q.931 octet containing the type-of-number and
 				numbering-plan-identification fields.</para>
 			</parameter>
+			<parameter name="ChargedItem">
+				<para>Defines what part of the call is charged in AOC-S. Usually this is set to
+				BasicCommunication, which refers to the time after the call is answered, but establishment
+				(CallAttempt) or successful establishment (CallSetup) of a call can also be used.
+				Other options are available, but these generally do not carry enough information to actually
+				calculate the price of a call.
+				It is possible to have multiple ChargedItem entries for a single call -- for example to
+				charge for both the establishment of the call and the actual call. In this case, each
+				ChargedItem is described by a ChargedItem: header and all other headers that follow it up to
+				the next ChargedItem: header.</para>
+				<enumlist>
+					<enum name="NA" />
+					<enum name="SpecialArrangement" />
+					<enum name="BasicCommunication" />
+					<enum name="CallAttempt" />
+					<enum name="CallSetup" />
+					<enum name="UserUserInfo" />
+					<enum name="SupplementaryService" />
+				</enumlist>
+			</parameter>
+			<parameter name="RateType">
+				<para>Defines how an AOC-S ChargedItem is charged.
+				The Duration option is only available when ChargedItem==BasicCommunication.</para>
+				<enumlist>
+					<enum name="NA" />
+					<enum name="Free" />
+					<enum name="FreeFromBeginning" />
+					<enum name="Duration" />
+					<enum name="Flat" />
+					<enum name="Volume" />
+					<enum name="SpecialCode" />
+				</enumlist>
+			</parameter>
+			<parameter name="Time">
+				<para>Specifies a positive integer which is the amount of time is paid for by one
+				CurrencyAmount.
+				This value is required when RateType==Duration.</para>
+			</parameter>
+			<parameter name="TimeScale">
+				<para>Specifies the time multiplier.
+				This value is required when Time is given.</para>
+				<enumlist>
+					<enum name="OneHundredthSecond" />
+					<enum name="OneTenthSecond" />
+					<enum name="Second" />
+					<enum name="TenSeconds" />
+					<enum name="Minute" />
+					<enum name="Hour" />
+					<enum name="Day" />
+				</enumlist>
+			</parameter>
+			<parameter name="Granularity">
+				<para>Specifies a positive integer which is the size of the charged time increments.
+				This value is optional when RateType==Duration and ChargingType==StepFunction.</para>
+			</parameter>
+			<parameter name="GranularityTimeScale">
+				<para>Specifies the granularity time multiplier.
+				This value is required when Granularity is given.</para>
+				<enumlist>
+					<enum name="OneHundredthSecond" />
+					<enum name="OneTenthSecond" />
+					<enum name="Second" />
+					<enum name="TenSeconds" />
+					<enum name="Minute" />
+					<enum name="Hour" />
+					<enum name="Day" />
+				</enumlist>
+			</parameter>
+			<parameter name="ChargingType">
+				<para>Specifies whether the charge increases continuously with time or in increments of
+				Time or, if provided, Granularity.
+				This value is required when RateType==Duration.</para>
+				<enumlist>
+					<enum name="ContinuousCharging" />
+					<enum name="StepFunction" />
+				</enumlist>
+			</parameter>
+			<parameter name="VolumeUnit">
+				<para>Specifies the quantity of which one unit is paid for by one CurrencyAmount.
+				This value is required when RateType==Volume.</para>
+				<enumlist>
+					<enum name="Octet" />
+					<enum name="Segment" />
+					<enum name="Message" />
+				</enumlist>
+			</parameter>
+			<parameter name="Code">
+				<para>Specifies the charging code, which can be set to a value between 1 and 10.
+				This value is required when ChargedItem==SpecialArrangement or RateType==SpecialCode.</para>
+			</parameter>
 		</syntax>
 		<description>
-			<para>Generates an AOC-D or AOC-E message on a channel.</para>
+			<para>Generates an AOC-S, AOC-D or AOC-E message on a channel.</para>
 		</description>
 		<see-also>
+			<ref type="managerEvent">AOC-S</ref>
 			<ref type="managerEvent">AOC-D</ref>
 			<ref type="managerEvent">AOC-E</ref>
 		</see-also>
@@ -1277,21 +1379,6 @@
 			this command can be used to create filters that may bypass
 			filters defined in manager.conf</para>
 		</description>
-		<see-also>
-			<ref type="manager">FilterList</ref>
-		</see-also>
-	</manager>
-	<manager name="FilterList" language="en_US">
-		<synopsis>
-			Show current event filters for this session
-		</synopsis>
-		<description>
-			<para>The filters displayed are for the current session.  Only those filters defined in
-                        manager.conf will be present upon starting a new session.</para>
-		</description>
-		<see-also>
-			<ref type="manager">Filter</ref>
-		</see-also>
 	</manager>
 	<manager name="BlindTransfer" language="en_US">
 		<synopsis>
@@ -1499,6 +1586,11 @@ static struct stasis_forward *rtp_topic_forwarder;
 /*! \brief The \ref stasis_subscription for forwarding the Security topic to the AMI topic */
 static struct stasis_forward *security_topic_forwarder;
 
+/*!
+ * \brief Set to true (non-zero) to globally allow all dangerous AMI actions to run
+ */
+static int live_dangerously;
+
 #ifdef TEST_FRAMEWORK
 /*! \brief The \ref stasis_subscription for forwarding the Test topic to the AMI topic */
 static struct stasis_forward *test_suite_forwarder;
@@ -3618,6 +3710,29 @@ static int action_ping(struct mansession *s, const struct message *m)
 	return 0;
 }
 
+void astman_live_dangerously(int new_live_dangerously)
+{
+	if (new_live_dangerously && !live_dangerously)
+	{
+		ast_log(LOG_WARNING, "Manager Configuration load protection disabled.\n");
+	}
+
+	if (!new_live_dangerously && live_dangerously)
+	{
+		ast_log(LOG_NOTICE, "Manager Configuration load protection enabled.\n");
+	}
+	live_dangerously = new_live_dangerously;
+}
+
+static int restrictedFile(const char *filename)
+{
+	if (!live_dangerously && !strncasecmp(filename, "/", 1) &&
+		 strncasecmp(filename, ast_config_AST_CONFIG_DIR, strlen(ast_config_AST_CONFIG_DIR))) {
+		return 1;
+	}
+	return 0;
+}
+
 static int action_getconfig(struct mansession *s, const struct message *m)
 {
 	struct ast_config *cfg;
@@ -3636,6 +3751,11 @@ static int action_getconfig(struct mansession *s, const struct message *m)
 		return 0;
 	}
 
+	if (restrictedFile(fn)) {
+		astman_send_error(s, m, "File requires escalated priveledges");
+		return 0;
+	}
+
 	cfg = ast_config_load2(fn, "manager", config_flags);
 	if (cfg == CONFIG_STATUS_FILEMISSING) {
 		astman_send_error(s, m, "Config file not found");
@@ -3763,6 +3883,11 @@ static int action_getconfigjson(struct mansession *s, const struct message *m)
 		return 0;
 	}
 
+	if (restrictedFile(fn)) {
+		astman_send_error(s, m, "File requires escalated priveledges");
+		return 0;
+	}
+
 	if (!(cfg = ast_config_load2(fn, "manager", config_flags))) {
 		astman_send_error(s, m, "Config file not found");
 		return 0;
@@ -4114,6 +4239,10 @@ static int action_updateconfig(struct mansession *s, const struct message *m)
 		astman_send_error(s, m, "Filename not specified");
 		return 0;
 	}
+	if (restrictedFile(sfn) || restrictedFile(dfn)) {
+		astman_send_error(s, m, "File requires escalated priveledges");
+		return 0;
+	}
 	if (!(cfg = ast_config_load2(sfn, "manager", config_flags))) {
 		astman_send_error(s, m, "Config file not found");
 		return 0;
@@ -5515,10 +5644,8 @@ static int aocmessage_get_unit_entry(const struct message *m, struct ast_aoc_uni
 	return 0;
 }
 
-static int action_aocmessage(struct mansession *s, const struct message *m)
+static struct ast_aoc_decoded *action_aoc_de_message(struct mansession *s, const struct message *m)
 {
-	const char *channel = astman_get_header(m, "Channel");
-	const char *pchannel = astman_get_header(m, "ChannelPrefix");
 	const char *msgtype = astman_get_header(m, "MsgType");
 	const char *chargetype = astman_get_header(m, "ChargeType");
 	const char *currencyname = astman_get_header(m, "CurrencyName");
@@ -5538,30 +5665,8 @@ static int action_aocmessage(struct mansession *s, const struct message *m)
 	unsigned int _currencyamount = 0;
 	int _association_id = 0;
 	unsigned int _association_plan = 0;
-	struct ast_channel *chan = NULL;
 
 	struct ast_aoc_decoded *decoded = NULL;
-	struct ast_aoc_encoded *encoded = NULL;
-	size_t encoded_size = 0;
-
-	if (ast_strlen_zero(channel) && ast_strlen_zero(pchannel)) {
-		astman_send_error(s, m, "Channel and PartialChannel are not specified. Specify at least one of these.");
-		goto aocmessage_cleanup;
-	}
-
-	if (!(chan = ast_channel_get_by_name(channel)) && !ast_strlen_zero(pchannel)) {
-		chan = ast_channel_get_by_name_prefix(pchannel, strlen(pchannel));
-	}
-
-	if (!chan) {
-		astman_send_error(s, m, "No such channel");
-		goto aocmessage_cleanup;
-	}
-
-	if (ast_strlen_zero(msgtype) || (strcasecmp(msgtype, "d") && strcasecmp(msgtype, "e"))) {
-		astman_send_error(s, m, "Invalid MsgType");
-		goto aocmessage_cleanup;
-	}
 
 	if (ast_strlen_zero(chargetype)) {
 		astman_send_error(s, m, "ChargeType not specified");
@@ -5702,8 +5807,324 @@ static int action_aocmessage(struct mansession *s, const struct message *m)
 	ast_aoc_set_billing_id(decoded, _billingid);
 	ast_aoc_set_total_type(decoded, _totaltype);
 
+	return decoded;
+
+aocmessage_cleanup:
+
+	ast_aoc_destroy_decoded(decoded);
+	return NULL;
+}
+
+static int action_aoc_s_submessage(struct mansession *s, const struct message *m,
+		struct ast_aoc_decoded *decoded)
+{
+	const char *chargeditem = __astman_get_header(m, "ChargedItem", GET_HEADER_LAST_MATCH);
+	const char *ratetype = __astman_get_header(m, "RateType", GET_HEADER_LAST_MATCH);
+	const char *currencyname = __astman_get_header(m, "CurrencyName", GET_HEADER_LAST_MATCH);
+	const char *currencyamount = __astman_get_header(m, "CurrencyAmount", GET_HEADER_LAST_MATCH);
+	const char *mult = __astman_get_header(m, "CurrencyMultiplier", GET_HEADER_LAST_MATCH);
+	const char *time = __astman_get_header(m, "Time", GET_HEADER_LAST_MATCH);
+	const char *timescale = __astman_get_header(m, "TimeScale", GET_HEADER_LAST_MATCH);
+	const char *granularity = __astman_get_header(m, "Granularity", GET_HEADER_LAST_MATCH);
+	const char *granularitytimescale = __astman_get_header(m, "GranularityTimeScale", GET_HEADER_LAST_MATCH);
+	const char *chargingtype = __astman_get_header(m, "ChargingType", GET_HEADER_LAST_MATCH);
+	const char *volumeunit = __astman_get_header(m, "VolumeUnit", GET_HEADER_LAST_MATCH);
+	const char *code = __astman_get_header(m, "Code", GET_HEADER_LAST_MATCH);
+
+	enum ast_aoc_s_charged_item _chargeditem;
+	enum ast_aoc_s_rate_type _ratetype;
+	enum ast_aoc_currency_multiplier _mult = AST_AOC_MULT_ONE;
+	unsigned int _currencyamount = 0;
+	unsigned int _code;
+	unsigned int _time = 0;
+	enum ast_aoc_time_scale _scale = 0;
+	unsigned int _granularity = 0;
+	enum ast_aoc_time_scale _granularity_time_scale = AST_AOC_TIME_SCALE_MINUTE;
+	int _step = 0;
+	enum ast_aoc_volume_unit _volumeunit = 0;
+
+	if (ast_strlen_zero(chargeditem)) {
+		astman_send_error(s, m, "ChargedItem not specified");
+		goto aocmessage_cleanup;
+	}
+
+	if (ast_strlen_zero(ratetype)) {
+		astman_send_error(s, m, "RateType not specified");
+		goto aocmessage_cleanup;
+	}
+
+	if (!strcasecmp(chargeditem, "NA")) {
+		_chargeditem = AST_AOC_CHARGED_ITEM_NA;
+	} else if (!strcasecmp(chargeditem, "SpecialArrangement")) {
+		_chargeditem = AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT;
+	} else if (!strcasecmp(chargeditem, "BasicCommunication")) {
+		_chargeditem = AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION;
+	} else if (!strcasecmp(chargeditem, "CallAttempt")) {
+		_chargeditem = AST_AOC_CHARGED_ITEM_CALL_ATTEMPT;
+	} else if (!strcasecmp(chargeditem, "CallSetup")) {
+		_chargeditem = AST_AOC_CHARGED_ITEM_CALL_SETUP;
+	} else if (!strcasecmp(chargeditem, "UserUserInfo")) {
+		_chargeditem = AST_AOC_CHARGED_ITEM_USER_USER_INFO;
+	} else if (!strcasecmp(chargeditem, "SupplementaryService")) {
+		_chargeditem = AST_AOC_CHARGED_ITEM_SUPPLEMENTARY_SERVICE;
+	} else {
+		astman_send_error(s, m, "Invalid ChargedItem");
+		goto aocmessage_cleanup;
+	}
+
+	if (!strcasecmp(ratetype, "NA")) {
+		_ratetype = AST_AOC_RATE_TYPE_NA;
+	} else if (!strcasecmp(ratetype, "Free")) {
+		_ratetype = AST_AOC_RATE_TYPE_FREE;
+	} else if (!strcasecmp(ratetype, "FreeFromBeginning")) {
+		_ratetype = AST_AOC_RATE_TYPE_FREE_FROM_BEGINNING;
+	} else if (!strcasecmp(ratetype, "Duration")) {
+		_ratetype = AST_AOC_RATE_TYPE_DURATION;
+	} else if (!strcasecmp(ratetype, "Flat")) {
+		_ratetype = AST_AOC_RATE_TYPE_FLAT;
+	} else if (!strcasecmp(ratetype, "Volume")) {
+		_ratetype = AST_AOC_RATE_TYPE_VOLUME;
+	} else if (!strcasecmp(ratetype, "SpecialCode")) {
+		_ratetype = AST_AOC_RATE_TYPE_SPECIAL_CODE;
+	} else {
+		astman_send_error(s, m, "Invalid RateType");
+		goto aocmessage_cleanup;
+	}
+
+	if (_ratetype > AST_AOC_RATE_TYPE_FREE_FROM_BEGINNING) {
+		if (ast_strlen_zero(currencyamount) || (sscanf(currencyamount, "%30u",
+				&_currencyamount) != 1)) {
+			astman_send_error(s, m, "Invalid CurrencyAmount, CurrencyAmount is a required when RateType is non-free");
+			goto aocmessage_cleanup;
+		}
 
-	if ((encoded = ast_aoc_encode(decoded, &encoded_size, NULL)) && !ast_indicate_data(chan, AST_CONTROL_AOC, encoded, encoded_size)) {
+		if (ast_strlen_zero(mult)) {
+			astman_send_error(s, m, "ChargeMultiplier unspecified, ChargeMultiplier is required when ChargeType is Currency.");
+			goto aocmessage_cleanup;
+		} else if (!strcasecmp(mult, "onethousandth")) {
+			_mult = AST_AOC_MULT_ONETHOUSANDTH;
+		} else if (!strcasecmp(mult, "onehundredth")) {
+			_mult = AST_AOC_MULT_ONEHUNDREDTH;
+		} else if (!strcasecmp(mult, "onetenth")) {
+			_mult = AST_AOC_MULT_ONETENTH;
+		} else if (!strcasecmp(mult, "one")) {
+			_mult = AST_AOC_MULT_ONE;
+		} else if (!strcasecmp(mult, "ten")) {
+			_mult = AST_AOC_MULT_TEN;
+		} else if (!strcasecmp(mult, "hundred")) {
+			_mult = AST_AOC_MULT_HUNDRED;
+		} else if (!strcasecmp(mult, "thousand")) {
+			_mult = AST_AOC_MULT_THOUSAND;
+		} else {
+			astman_send_error(s, m, "Invalid ChargeMultiplier");
+			goto aocmessage_cleanup;
+		}
+	}
+
+	if (_ratetype == AST_AOC_RATE_TYPE_DURATION) {
+		if (ast_strlen_zero(timescale)) {
+			astman_send_error(s, m, "TimeScale unspecified, TimeScale is required when RateType is Duration.");
+			goto aocmessage_cleanup;
+		} else if (!strcasecmp(timescale, "onehundredthsecond")) {
+			_scale = AST_AOC_TIME_SCALE_HUNDREDTH_SECOND;
+		} else if (!strcasecmp(timescale, "onetenthsecond")) {
+			_scale = AST_AOC_TIME_SCALE_TENTH_SECOND;
+		} else if (!strcasecmp(timescale, "second")) {
+			_scale = AST_AOC_TIME_SCALE_SECOND;
+		} else if (!strcasecmp(timescale, "tenseconds")) {
+			_scale = AST_AOC_TIME_SCALE_TEN_SECOND;
+		} else if (!strcasecmp(timescale, "minute")) {
+			_scale = AST_AOC_TIME_SCALE_MINUTE;
+		} else if (!strcasecmp(timescale, "hour")) {
+			_scale = AST_AOC_TIME_SCALE_HOUR;
+		} else if (!strcasecmp(timescale, "day")) {
+			_scale = AST_AOC_TIME_SCALE_DAY;
+		} else {
+			astman_send_error(s, m, "Invalid TimeScale");
+			goto aocmessage_cleanup;
+		}
+
+		if (ast_strlen_zero(time) || (sscanf(time, "%30u", &_time) != 1)) {
+			astman_send_error(s, m, "Invalid Time, Time is a required when RateType is Duration");
+			goto aocmessage_cleanup;
+		}
+
+		if (!ast_strlen_zero(granularity)) {
+			if ((sscanf(time, "%30u", &_granularity) != 1)) {
+				astman_send_error(s, m, "Invalid Granularity");
+				goto aocmessage_cleanup;
+			}
+
+			if (ast_strlen_zero(granularitytimescale)) {
+				astman_send_error(s, m, "Invalid GranularityTimeScale, GranularityTimeScale is a required when Granularity is specified");
+			} else if (!strcasecmp(granularitytimescale, "onehundredthsecond")) {
+				_granularity_time_scale = AST_AOC_TIME_SCALE_HUNDREDTH_SECOND;
+			} else if (!strcasecmp(granularitytimescale, "onetenthsecond")) {
+				_granularity_time_scale = AST_AOC_TIME_SCALE_TENTH_SECOND;
+			} else if (!strcasecmp(granularitytimescale, "second")) {
+				_granularity_time_scale = AST_AOC_TIME_SCALE_SECOND;
+			} else if (!strcasecmp(granularitytimescale, "tenseconds")) {
+				_granularity_time_scale = AST_AOC_TIME_SCALE_TEN_SECOND;
+			} else if (!strcasecmp(granularitytimescale, "minute")) {
+				_granularity_time_scale = AST_AOC_TIME_SCALE_MINUTE;
+			} else if (!strcasecmp(granularitytimescale, "hour")) {
+				_granularity_time_scale = AST_AOC_TIME_SCALE_HOUR;
+			} else if (!strcasecmp(granularitytimescale, "day")) {
+				_granularity_time_scale = AST_AOC_TIME_SCALE_DAY;
+			} else {
+				astman_send_error(s, m, "Invalid GranularityTimeScale");
+				goto aocmessage_cleanup;
+			}
+		}
+
+		if (ast_strlen_zero(chargingtype) || strcasecmp(chargingtype, "continuouscharging") == 0) {
+			_step = 0;
+		} else if (strcasecmp(chargingtype, "stepfunction") == 0 ) {
+			_step = 1;
+		} else {
+			astman_send_error(s, m, "Invalid ChargingType");
+			goto aocmessage_cleanup;
+		}
+	}
+
+	if (_ratetype == AST_AOC_RATE_TYPE_VOLUME) {
+		if (ast_strlen_zero(volumeunit)) {
+			astman_send_error(s, m, "VolumeUnit unspecified, VolumeUnit is required when RateType is Volume.");
+			goto aocmessage_cleanup;
+		} else if (!strcasecmp(timescale, "octet")) {
+			_volumeunit = AST_AOC_VOLUME_UNIT_OCTET;
+		} else if (!strcasecmp(timescale, "segment")) {
+			_volumeunit = AST_AOC_VOLUME_UNIT_SEGMENT;
+		} else if (!strcasecmp(timescale, "message")) {
+			_volumeunit = AST_AOC_VOLUME_UNIT_MESSAGE;
+		}else {
+			astman_send_error(s, m, "Invalid VolumeUnit");
+			goto aocmessage_cleanup;
+		}
+	}
+
+	if (_chargeditem == AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT
+			|| _ratetype == AST_AOC_RATE_TYPE_SPECIAL_CODE) {
+		if (ast_strlen_zero(code) || (sscanf(code, "%30u", &_code) != 1)) {
+			astman_send_error(s, m, "Invalid Code, Code is a required when ChargedItem is SpecialArrangement and when RateType is SpecialCode");
+			goto aocmessage_cleanup;
+		}
+	}
+
+	if (_chargeditem == AST_AOC_CHARGED_ITEM_SPECIAL_ARRANGEMENT) {
+		ast_aoc_s_add_special_arrangement(decoded, _code);
+	} else if (_ratetype == AST_AOC_RATE_TYPE_DURATION) {
+		ast_aoc_s_add_rate_duration(decoded, _chargeditem, _currencyamount, _mult,
+			currencyname, _time, _scale, _granularity, _granularity_time_scale, _step);
+	} else if (_ratetype == AST_AOC_RATE_TYPE_FLAT) {
+		ast_aoc_s_add_rate_flat(decoded, _chargeditem, _currencyamount, _mult,
+				currencyname);
+	} else if (_ratetype == AST_AOC_RATE_TYPE_VOLUME) {
+		ast_aoc_s_add_rate_volume(decoded, _chargeditem, _volumeunit, _currencyamount,
+			_mult, currencyname);
+	} else if (_ratetype == AST_AOC_RATE_TYPE_SPECIAL_CODE) {
+		ast_aoc_s_add_rate_special_charge_code(decoded, _chargeditem, _code);
+	} else if (_ratetype == AST_AOC_RATE_TYPE_FREE) {
+		ast_aoc_s_add_rate_free(decoded, _chargeditem, 0);
+	} else if (_ratetype == AST_AOC_RATE_TYPE_FREE_FROM_BEGINNING) {
+		ast_aoc_s_add_rate_free(decoded, _chargeditem, 1);
+	} else if (_ratetype == AST_AOC_RATE_TYPE_NA) {
+		ast_aoc_s_add_rate_na(decoded, _chargeditem);
+	}
+
+	return 0;
+
+aocmessage_cleanup:
+
+	return -1;
+}
+
+static struct ast_aoc_decoded *action_aoc_s_message(struct mansession *s,
+		const struct message *m)
+{
+	struct ast_aoc_decoded *decoded = NULL;
+	int hdrlen;
+	int x;
+	static const char hdr[] = "ChargedItem:";
+	struct message sm = { 0 };
+	int rates = 0;
+
+	if (!(decoded = ast_aoc_create(AST_AOC_S, 0, 0))) {
+		astman_send_error(s, m, "Message Creation Failed");
+		goto aocmessage_cleanup;
+	}
+
+	hdrlen = strlen(hdr);
+	for (x = 0; x < m->hdrcount; x++) {
+		if (strncasecmp(hdr, m->headers[x], hdrlen) == 0) {
+			if (rates > ast_aoc_s_get_count(decoded)) {
+				if (action_aoc_s_submessage(s, &sm, decoded) == -1) {
+					goto aocmessage_cleanup;
+				}
+			}
+			++rates;
+		}
+
+		sm.headers[sm.hdrcount] = m->headers[x];
+		++sm.hdrcount;
+	}
+	if (rates > ast_aoc_s_get_count(decoded)) {
+		if (action_aoc_s_submessage(s, &sm, decoded) == -1) {
+			goto aocmessage_cleanup;
+		}
+	}
+
+	return decoded;
+
+aocmessage_cleanup:
+
+	ast_aoc_destroy_decoded(decoded);
+	return NULL;
+}
+
+static int action_aocmessage(struct mansession *s, const struct message *m)
+{
+	const char *msgtype = astman_get_header(m, "MsgType");
+	const char *channel = astman_get_header(m, "Channel");
+	const char *pchannel = astman_get_header(m, "ChannelPrefix");
+
+	struct ast_channel *chan = NULL;
+
+	struct ast_aoc_decoded *decoded = NULL;
+	struct ast_aoc_encoded *encoded = NULL;
+	size_t encoded_size = 0;
+
+	if (ast_strlen_zero(channel) && ast_strlen_zero(pchannel)) {
+		astman_send_error(s, m, "Channel and PartialChannel are not specified. Specify at least one of these.");
+		goto aocmessage_cleanup;
+	}
+
+	if (!(chan = ast_channel_get_by_name(channel)) && !ast_strlen_zero(pchannel)) {
+		chan = ast_channel_get_by_name_prefix(pchannel, strlen(pchannel));
+	}
+
+	if (!chan) {
+		astman_send_error(s, m, "No such channel");
+		goto aocmessage_cleanup;
+	}
+
+	if (strcasecmp(msgtype, "d") == 0 || strcasecmp(msgtype, "e") == 0) {
+		decoded = action_aoc_de_message(s, m);
+	}
+	else if (strcasecmp(msgtype, "s") == 0) {
+		decoded = action_aoc_s_message(s, m);
+	}
+	else {
+		astman_send_error(s, m, "Invalid MsgType");
+		goto aocmessage_cleanup;
+	}
+
+	if (!decoded) {
+		goto aocmessage_cleanup;
+	}
+
+	if ((encoded = ast_aoc_encode(decoded, &encoded_size, chan))
+			&& !ast_indicate_data(chan, AST_CONTROL_AOC, encoded, encoded_size)) {
 		astman_send_ack(s, m, "AOC Message successfully queued on channel");
 	} else {
 		astman_send_error(s, m, "Error encoding AOC message, could not queue onto channel");
@@ -5853,14 +6274,12 @@ static int action_originate(struct mansession *s, const struct message *m)
 		old = vars;
 		vars = NULL;
 
-		/* The variables in the AMI originate action are appended at the end of the list, to override any user variables that apply*/
+		/* The variables in the AMI originate action are appended at the end of the list, to override any user variables that apply */
 
 		vars = ast_variables_dup(s->session->chanvars);
 		if (old) {
 			for (v = vars; v->next; v = v->next );
-			if (v->next) {
-				v->next = old;	/* Append originate variables at end of list */
-			}
+			v->next = old;	/* Append originate variables at end of list */
 		}
 	}
 
@@ -6198,7 +6617,7 @@ static int match_filter(struct mansession *s, char *eventdata)
 	if (manager_debug) {
 		ast_verbose("<-- Examining AMI event: -->\n%s\n", eventdata);
 	} else {
-		ast_debug(3, "Examining AMI event:\n%s\n", eventdata);
+		ast_debug(4, "Examining AMI event:\n%s\n", eventdata);
 	}
 	if (!ao2_container_count(s->session->whitefilters) && !ao2_container_count(s->session->blackfilters)) {
 		return 1; /* no filtering means match all */
@@ -6242,7 +6661,7 @@ static int process_events(struct mansession *s)
 			    (s->session->readperm & eqe->category) == eqe->category &&
 			    (s->session->send_events & eqe->category) == eqe->category) {
 					if (match_filter(s, eqe->eventdata)) {
-						if (send_string(s, eqe->eventdata) < 0)
+						if (send_string(s, eqe->eventdata) < 0 || s->write_error)
 							ret = -1;	/* don't send more */
 					}
 			}
@@ -7023,16 +7442,17 @@ done:
 }
 
 /*! \brief remove at most n_max stale session from the list. */
-static void purge_sessions(int n_max)
+static int purge_sessions(int n_max)
 {
 	struct ao2_container *sessions;
 	struct mansession_session *session;
 	time_t now = time(NULL);
 	struct ao2_iterator i;
+	int purged = 0;
 
 	sessions = ao2_global_obj_ref(mgr_sessions);
 	if (!sessions) {
-		return;
+		return 0;
 	}
 	i = ao2_iterator_init(sessions, 0);
 	ao2_ref(sessions, -1);
@@ -7048,12 +7468,14 @@ static void purge_sessions(int n_max)
 			ao2_unlock(session);
 			session_destroy(session);
 			n_max--;
+			purged++;
 		} else {
 			ao2_unlock(session);
 			unref_mansession(session);
 		}
 	}
 	ao2_iterator_destroy(&i);
+	return purged;
 }
 
 /*! \brief
@@ -7122,6 +7544,14 @@ static int __attribute__((format(printf, 9, 0))) __manager_event_sessions_va(
 	struct ast_str *buf;
 	int i;
 
+	if (!ast_strlen_zero(manager_disabledevents)) {
+		if (ast_in_delimited_string(event, manager_disabledevents, ',')) {
+			ast_debug(3, "AMI Event '%s' is globally disabled, skipping\n", event);
+			/* Event is globally disabled */
+			return -1;
+		}
+	}
+
 	buf = ast_str_thread_get(&manager_event_buf, MANAGER_EVENT_BUF_INITSIZE);
 	if (!buf) {
 		return -1;
@@ -7233,15 +7663,6 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount
 	va_list ap;
 	int res;
 
-	if (!ast_strlen_zero(manager_disabledevents)) {
-		if (ast_in_delimited_string(event, manager_disabledevents, ',')) {
-			ast_debug(3, "AMI Event '%s' is globally disabled, skipping\n", event);
-			/* Event is globally disabled */
-			ao2_cleanup(sessions);
-			return 0;
-		}
-	}
-
 	if (!any_manager_listeners(sessions)) {
 		/* Nobody is listening */
 		ao2_cleanup(sessions);
@@ -8647,7 +9068,17 @@ static int webregged = 0;
  */
 static void purge_old_stuff(void *data)
 {
-	purge_sessions(1);
+	struct ast_tcptls_session_args *ser = data;
+	/* purge_sessions will return the number of sessions actually purged,
+	 * up to a maximum of it's arguments, purge one at a time, keeping a
+	 * purge interval of 1ms as long as we purged a session, otherwise
+	 * revert to a purge check every 5s
+	 */
+	if (purge_sessions(1) == 1) {
+		ser->poll_timeout = 1;
+	} else {
+		ser->poll_timeout = 5000;
+	}
 	purge_events();
 }
 
@@ -8688,6 +9119,7 @@ static char *handle_manager_show_settings(struct ast_cli_entry *e, int cmd, stru
 	}
 #define FORMAT "  %-25.25s  %-15.55s\n"
 #define FORMAT2 "  %-25.25s  %-15d\n"
+#define FORMAT3 "  %-25.25s  %s\n"
 	if (a->argc != 3) {
 		return CLI_SHOWUSAGE;
 	}
@@ -8705,11 +9137,12 @@ static char *handle_manager_show_settings(struct ast_cli_entry *e, int cmd, stru
 	ast_cli(a->fd, FORMAT, "Allow multiple login:", AST_CLI_YESNO(allowmultiplelogin));
 	ast_cli(a->fd, FORMAT, "Display connects:", AST_CLI_YESNO(displayconnects));
 	ast_cli(a->fd, FORMAT, "Timestamp events:", AST_CLI_YESNO(timestampevents));
-	ast_cli(a->fd, FORMAT, "Channel vars:", S_OR(manager_channelvars, ""));
-	ast_cli(a->fd, FORMAT, "Disabled events:", S_OR(manager_disabledevents, ""));
+	ast_cli(a->fd, FORMAT3, "Channel vars:", S_OR(manager_channelvars, ""));
+	ast_cli(a->fd, FORMAT3, "Disabled events:", S_OR(manager_disabledevents, ""));
 	ast_cli(a->fd, FORMAT, "Debug:", AST_CLI_YESNO(manager_debug));
 #undef FORMAT
 #undef FORMAT2
+#undef FORMAT3
 
 	return CLI_SUCCESS;
 }
diff --git a/main/message.c b/main/message.c
index a1e22ba4b980a1bc32e88210c2b236486843b4d6..0b737c192042f1ae511f64f23e712520512453f5 100644
--- a/main/message.c
+++ b/main/message.c
@@ -57,7 +57,7 @@
 					the recipient of the message that was received by Asterisk.</para>
 					<para>
 					</para>
-					<para>For an outgoing message, this will set the To header in the
+					<para>For an ourgoing message, this will set the To header in the
 					outgoing SIP message.  This may be overridden by the "to" parameter
 					of MessageSend.
 					</para>
@@ -67,7 +67,7 @@
 					incoming message, this will be set to the source of the message.</para>
 					<para>
 					</para>
-					<para>For an outgoing message, this will set the From header in the
+					<para>For an ourgoing message, this will set the From header in the
 					outgoing SIP message. This may be overridden by the "from" parameter
 					of MessageSend.
 					</para>
diff --git a/main/options.c b/main/options.c
index 07afd796d310a83953fcdde560c2ca429284ac5a..1507bc6ba5d56b2fc42fce10701aac5570f0fbe7 100644
--- a/main/options.c
+++ b/main/options.c
@@ -476,6 +476,7 @@ void load_asterisk_conf(void)
 	}
 	if (!ast_opt_remote) {
 		pbx_live_dangerously(live_dangerously);
+		astman_live_dangerously(live_dangerously);
 	}
 
 	option_debug += option_debug_new;
diff --git a/main/pbx.c b/main/pbx.c
index 0b307b9de5142c852d1e4bdd376301e497fc04bf..6aa3f5175747ed31346fd002ab4227ffa504b40d 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -280,21 +280,29 @@ struct scoreboard  /* make sure all fields are 0 before calling new_find_extensi
 	struct ast_exten *exten;
 };
 
-/*! \brief ast_context: An extension context - must remain in sync with fake_context */
+/*! \brief ast_context: An extension context */
 struct ast_context {
-	ast_rwlock_t lock;			/*!< A lock to prevent multiple threads from clobbering the context */
-	struct ast_exten *root;			/*!< The root of the list of extensions */
-	struct ast_hashtab *root_table;            /*!< For exact matches on the extensions in the pattern tree, and for traversals of the pattern_tree  */
-	struct match_char *pattern_tree;        /*!< A tree to speed up extension pattern matching */
-	struct ast_context *next;		/*!< Link them together */
-	struct ast_includes includes;		/*!< Include other contexts */
-	struct ast_ignorepats ignorepats;	/*!< Patterns for which to continue playing dialtone */
-	struct ast_sws alts;			/*!< Alternative switches */
-	char *registrar;			/*!< Registrar -- make sure you malloc this, as the registrar may have to survive module unloads */
-	int refcount;                   /*!< each module that would have created this context should inc/dec this as appropriate */
-	int autohints;                  /*!< Whether autohints support is enabled or not */
-	ast_mutex_t macrolock;			/*!< A lock to implement "exclusive" macros - held whilst a call is executing in the macro */
-	char name[0];				/*!< Name of the context */
+	const char *name;
+	const char *registrar;
+
+	ast_rwlock_t lock;                /*!< A lock to prevent multiple threads from clobbering the context */
+	struct ast_exten *root;           /*!< The root of the list of extensions */
+	struct ast_hashtab *root_table;   /*!< For exact matches on the extensions in the pattern tree, and for traversals of the pattern_tree  */
+	struct match_char *pattern_tree;  /*!< A tree to speed up extension pattern matching */
+	struct ast_context *next;         /*!< Link them together */
+	struct ast_includes includes;     /*!< Include other contexts */
+	struct ast_ignorepats ignorepats; /*!< Patterns for which to continue playing dialtone */
+	struct ast_sws alts;              /*!< Alternative switches */
+	int refcount;                     /*!< each module that would have created this context should inc/dec this as appropriate */
+	int autohints;                    /*!< Whether autohints support is enabled or not */
+	ast_mutex_t macrolock;            /*!< A lock to implement "exclusive" macros - held whilst a call is executing in the macro */
+
+	/*!
+	 * Buffer to hold the name & registrar character data.
+	 *
+	 * The context name *must* be stored first in this buffer.
+	 */
+	char data[];
 };
 
 /*! \brief ast_state_cb: An extension state notify register item */
@@ -1652,6 +1660,7 @@ static const char *get_pattern_node(struct pattern_node *node, const char *src,
 #undef INC_DST_OVERFLOW_CHECK
 }
 
+#define MAX_EXTENBUF_SIZE 512
 static struct match_char *add_exten_to_pattern_tree(struct ast_context *con, struct ast_exten *e1, int findonly)
 {
 	struct match_char *m1 = NULL;
@@ -1662,11 +1671,13 @@ static struct match_char *add_exten_to_pattern_tree(struct ast_context *con, str
 	int pattern = 0;
 	int idx_cur;
 	int idx_next;
-	char extenbuf[512];
+	char extenbuf[MAX_EXTENBUF_SIZE];
+	volatile size_t required_space = strlen(e1->exten) + 1;
 	struct pattern_node pat_node[2];
 
 	if (e1->matchcid) {
-		if (sizeof(extenbuf) < strlen(e1->exten) + strlen(e1->cidmatch) + 2) {
+		required_space += (strlen(e1->cidmatch) + 2 /* '/' + NULL */);
+		if (required_space > MAX_EXTENBUF_SIZE) {
 			ast_log(LOG_ERROR,
 				"The pattern %s/%s is too big to deal with: it will be ignored! Disaster!\n",
 				e1->exten, e1->cidmatch);
@@ -1674,7 +1685,13 @@ static struct match_char *add_exten_to_pattern_tree(struct ast_context *con, str
 		}
 		sprintf(extenbuf, "%s/%s", e1->exten, e1->cidmatch);/* Safe.  We just checked. */
 	} else {
-		ast_copy_string(extenbuf, e1->exten, sizeof(extenbuf));
+		if (required_space > MAX_EXTENBUF_SIZE) {
+			ast_log(LOG_ERROR,
+				"The pattern %s/%s is too big to deal with: it will be ignored! Disaster!\n",
+				e1->exten, e1->cidmatch);
+			return NULL;
+		}
+		ast_copy_string(extenbuf, e1->exten, required_space);
 	}
 
 #ifdef NEED_DEBUG
@@ -2426,35 +2443,18 @@ int ast_extension_close(const char *pattern, const char *data, int needmore)
 	return extension_match_core(pattern, data, needmore);
 }
 
-/* This structure must remain in sync with ast_context for proper hashtab matching */
-struct fake_context /* this struct is purely for matching in the hashtab */
-{
-	ast_rwlock_t lock;
-	struct ast_exten *root;
-	struct ast_hashtab *root_table;
-	struct match_char *pattern_tree;
-	struct ast_context *next;
-	struct ast_includes includes;
-	struct ast_ignorepats ignorepats;
-	struct ast_sws alts;
-	const char *registrar;
-	int refcount;
-	int autohints;
-	ast_mutex_t macrolock;
-	char name[256];
-};
-
 struct ast_context *ast_context_find(const char *name)
 {
 	struct ast_context *tmp;
-	struct fake_context item;
+	struct ast_context item = {
+		.name = name,
+	};
 
 	if (!name) {
 		return NULL;
 	}
 	ast_rdlock_contexts();
 	if (contexts_table) {
-		ast_copy_string(item.name, name, sizeof(item.name));
 		tmp = ast_hashtab_lookup(contexts_table, &item);
 	} else {
 		tmp = NULL;
@@ -2512,7 +2512,7 @@ struct ast_exten *pbx_find_extension(struct ast_channel *chan,
 		q->data = NULL;
 		q->foundcontext = NULL;
 	} else if (q->stacklen >= AST_PBX_MAX_STACK) {
-		ast_log(LOG_WARNING, "Maximum PBX stack exceeded\n");
+		ast_log(LOG_WARNING, "Maximum PBX stack (%d) exceeded. Too many includes?\n", AST_PBX_MAX_STACK);
 		return NULL;
 	}
 
@@ -2765,7 +2765,10 @@ struct ast_exten *pbx_find_extension(struct ast_channel *chan,
 			return NULL;
 		}
 	}
-	q->incstack[q->stacklen++] = tmp->name;	/* Setup the stack */
+	/* Technically we should be using tmp->name here, but if we used that we
+	 * would have to cast away the constness of the 'name' pointer and I do
+	 * not want to do that. */
+	q->incstack[q->stacklen++] = tmp->data;	/* Setup the stack */
 	/* Now try any includes we have in this context */
 	for (idx = 0; idx < ast_context_includes_count(tmp); idx++) {
 		const struct ast_include *i = ast_context_includes_get(tmp, idx);
@@ -4803,9 +4806,9 @@ void pbx_set_overrideswitch(const char *newval)
  */
 static struct ast_context *find_context(const char *context)
 {
-	struct fake_context item;
-
-	ast_copy_string(item.name, context, sizeof(item.name));
+	struct ast_context item = {
+		.name = context,
+	};
 
 	return ast_hashtab_lookup(contexts_table, &item);
 }
@@ -4818,9 +4821,9 @@ static struct ast_context *find_context(const char *context)
 static struct ast_context *find_context_locked(const char *context)
 {
 	struct ast_context *c;
-	struct fake_context item;
-
-	ast_copy_string(item.name, context, sizeof(item.name));
+	struct ast_context item = {
+		.name = context,
+	};
 
 	ast_rdlock_contexts();
 	c = ast_hashtab_lookup(contexts_table, &item);
@@ -6196,8 +6199,12 @@ void unreference_cached_app(struct ast_app *app)
 struct ast_context *ast_context_find_or_create(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *name, const char *registrar)
 {
 	struct ast_context *tmp, **local_contexts;
-	struct fake_context search;
-	int length = sizeof(struct ast_context) + strlen(name) + 1;
+	struct ast_context search = {
+		.name = name,
+	};
+	size_t name_bytes = strlen(name);
+	size_t registrar_bytes = strlen(registrar);
+	int length = sizeof(struct ast_context) + name_bytes + registrar_bytes + 2;
 
 	if (!contexts_table) {
 		/* Protect creation of contexts_table from reentrancy. */
@@ -6213,7 +6220,6 @@ struct ast_context *ast_context_find_or_create(struct ast_context **extcontexts,
 		ast_unlock_contexts();
 	}
 
-	ast_copy_string(search.name, name, sizeof(search.name));
 	if (!extcontexts) {
 		ast_rdlock_contexts();
 		local_contexts = &contexts;
@@ -6235,14 +6241,19 @@ struct ast_context *ast_context_find_or_create(struct ast_context **extcontexts,
 	if ((tmp = ast_calloc(1, length))) {
 		ast_rwlock_init(&tmp->lock);
 		ast_mutex_init(&tmp->macrolock);
-		strcpy(tmp->name, name);
+		tmp->name = memcpy(&tmp->data[0], name, name_bytes);
+		tmp->registrar = memcpy(&tmp->data[name_bytes + 1], registrar, registrar_bytes);
 		tmp->root = NULL;
 		tmp->root_table = NULL;
-		tmp->registrar = ast_strdup(registrar);
 		AST_VECTOR_INIT(&tmp->includes, 0);
 		AST_VECTOR_INIT(&tmp->ignorepats, 0);
 		AST_VECTOR_INIT(&tmp->alts, 0);
 		tmp->refcount = 1;
+
+		/* The context 'name' must be stored at the beginning of 'data.' The
+		 * order of subsequent strings (currently only 'registrar') is not
+		 * relevant. */
+		ast_assert(tmp->name == &tmp->data[0]);
 	} else {
 		ast_log(LOG_ERROR, "Danger! We failed to allocate a context for %s!\n", name);
 		if (!extcontexts) {
@@ -8069,9 +8080,6 @@ static void __ast_internal_context_destroy( struct ast_context *con)
 	AST_VECTOR_CALLBACK_VOID(&tmp->alts, sw_free);
 	AST_VECTOR_FREE(&tmp->alts);
 
-	if (tmp->registrar)
-		ast_free(tmp->registrar);
-
 	/* destroy the hash tabs */
 	if (tmp->root_table) {
 		ast_hashtab_destroy(tmp->root_table, 0);
@@ -8777,8 +8785,14 @@ int ast_context_verify_includes(struct ast_context *con)
 {
 	int idx;
 	int res = 0;
+	int includecount = ast_context_includes_count(con);
 
-	for (idx = 0; idx < ast_context_includes_count(con); idx++) {
+	if (includecount >= AST_PBX_MAX_STACK) {
+		ast_log(LOG_WARNING, "Context %s contains too many includes (%d). Maximum is %d.\n",
+			ast_get_context_name(con), includecount, AST_PBX_MAX_STACK);
+	}
+
+	for (idx = 0; idx < includecount; idx++) {
 		const struct ast_include *inc = ast_context_includes_get(con, idx);
 
 		if (ast_context_find(include_rname(inc))) {
@@ -8826,6 +8840,47 @@ int ast_async_goto_if_exists(struct ast_channel *chan, const char * context, con
 	return __ast_goto_if_exists(chan, context, exten, priority, 1);
 }
 
+int pbx_parse_location(struct ast_channel *chan, char **contextp, char **extenp, char **prip, int *ipri, int *mode, char *rest)
+{
+	char *context, *exten, *pri;
+	/* do the strsep before here, so we don't have to alloc and free */
+	if (!*extenp) {
+		/* Only a priority in this one */
+		*prip = *contextp;
+		*extenp = NULL;
+		*contextp = NULL;
+	} else if (!*prip) {
+		/* Only an extension and priority in this one */
+		*prip = *extenp;
+		*extenp = *contextp;
+		*contextp = NULL;
+	}
+	context = *contextp;
+	exten = *extenp;
+	pri = *prip;
+	if (mode) {
+		if (*pri == '+') {
+			*mode = 1;
+			pri++;
+		} else if (*pri == '-') {
+			*mode = -1;
+			pri++;
+		}
+	}
+	if ((rest && sscanf(pri, "%30d%1s", ipri, rest) != 1) || sscanf(pri, "%30d", ipri) != 1) {
+		*ipri = ast_findlabel_extension(chan, context ? context : ast_channel_context(chan),
+			exten ? exten : ast_channel_exten(chan), pri,
+			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL));
+		if (*ipri < 1) {
+			ast_log(LOG_WARNING, "Priority '%s' must be a number > 0, or valid label\n", pri);
+			return -1;
+		} else if (mode) {
+			*mode = 0;
+		}
+	}
+	return 0;
+}
+
 static int pbx_parseable_goto(struct ast_channel *chan, const char *goto_string, int async)
 {
 	char *exten, *pri, *context;
@@ -8842,31 +8897,9 @@ static int pbx_parseable_goto(struct ast_channel *chan, const char *goto_string,
 	context = strsep(&stringp, ",");	/* guaranteed non-null */
 	exten = strsep(&stringp, ",");
 	pri = strsep(&stringp, ",");
-	if (!exten) {	/* Only a priority in this one */
-		pri = context;
-		exten = NULL;
-		context = NULL;
-	} else if (!pri) {	/* Only an extension and priority in this one */
-		pri = exten;
-		exten = context;
-		context = NULL;
-	}
-	if (*pri == '+') {
-		mode = 1;
-		pri++;
-	} else if (*pri == '-') {
-		mode = -1;
-		pri++;
-	}
-	if (sscanf(pri, "%30d%1s", &ipri, rest) != 1) {
-		ipri = ast_findlabel_extension(chan, context ? context : ast_channel_context(chan),
-			exten ? exten : ast_channel_exten(chan), pri,
-			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL));
-		if (ipri < 1) {
-			ast_log(LOG_WARNING, "Priority '%s' must be a number > 0, or valid label\n", pri);
-			return -1;
-		} else
-			mode = 0;
+
+	if (pbx_parse_location(chan, &context, &exten, &pri, &ipri, &mode, rest)) {
+		return -1;
 	}
 	/* At this point we have a priority and maybe an extension and a context */
 
diff --git a/main/pbx_app.c b/main/pbx_app.c
index 0cbb04a8b888e69255083bcfff773c8c07bb2ae0..6726a900b6fb4e47136bf8bc9713231fbf1a0a71 100644
--- a/main/pbx_app.c
+++ b/main/pbx_app.c
@@ -498,6 +498,33 @@ int pbx_exec(struct ast_channel *c,	/*!< Channel */
 	return res;
 }
 
+int ast_pbx_exec_application(struct ast_channel *chan, const char *app_name, const char *app_args)
+{
+	int res = -1;
+	struct ast_app *app;
+
+	app = pbx_findapp(app_name);
+	if (!app) {
+		ast_log(LOG_WARNING, "Could not find application (%s)\n", app_name);
+	} else {
+		struct ast_str *substituted_args = NULL;
+
+		if (!ast_strlen_zero(app_args) && (substituted_args = ast_str_create(16))) {
+			ast_str_substitute_variables(&substituted_args, 0, chan, app_args);
+			res = pbx_exec(chan, app, ast_str_buffer(substituted_args));
+			ast_free(substituted_args);
+		} else {
+			if (!ast_strlen_zero(app_args)) {
+				ast_log(LOG_WARNING, "Could not substitute application argument variables for %s\n", app_name);
+			}
+			res = pbx_exec(chan, app, app_args);
+		}
+		/* Manually make a snapshot now, since pbx_exec won't necessarily get called again immediately. */
+		ast_channel_publish_snapshot(chan);
+	}
+	return res;
+}
+
 static struct ast_cli_entry app_cli[] = {
 	AST_CLI_DEFINE(handle_show_applications, "Shows registered dialplan applications"),
 	AST_CLI_DEFINE(handle_show_application, "Describe a specific dialplan application"),
diff --git a/main/pbx_builtins.c b/main/pbx_builtins.c
index 54fe4ff92a88fdc7c972ca938044fef3f9fe93b2..fa6e63d1849af0d5cddde230fb50dbbeaa5476ea 100644
--- a/main/pbx_builtins.c
+++ b/main/pbx_builtins.c
@@ -49,11 +49,23 @@
 			<parameter name="delay">
 				<para>Asterisk will wait this number of milliseconds before returning to
 				the dialplan after answering the call.</para>
+				<para>The minimum is 500 ms. To answer immediately without waiting for media,
+				use the i option.</para>
+			</parameter>
+			<parameter name="options">
+				<optionlist>
+					<option name="i">
+						<para>Answer the channel immediately without waiting for media.</para>
+					</option>
+				</optionlist>
 			</parameter>
 		</syntax>
 		<description>
 			<para>If the call has not been answered, this application will
 			answer it. Otherwise, it has no effect on the call.</para>
+			<para>By default, Asterisk will wait for media for up to 500 ms, or
+			the user specified delay, whichever is longer. If you do not want
+			to wait for media at all, use the i option.</para>
 		</description>
 		<see-also>
 			<ref type="application">Hangup</ref>
@@ -83,6 +95,9 @@
 						<para>Only break if a digit hit matches a one digit
 						extension in the destination context.</para>
 					</option>
+					<option name="p">
+						<para>Do not allow playback to be interrupted with digits.</para>
+					</option>
 				</optionlist>
 			</parameter>
 			<parameter name="langoverride">
@@ -698,6 +713,10 @@
 							be used instead if set</emphasis></para>
 						</argument>
 					</option>
+					<option name="d">
+						<para>Play <literal>dial</literal> indications tone on channel while waiting
+						for digits.</para>
+					</option>
 				</optionlist>
 			</parameter>
 		</syntax>
@@ -829,6 +848,11 @@ static int pbx_builtin_answer(struct ast_channel *chan, const char *data)
 		delay = 0;
 	}
 
+	if (!ast_strlen_zero(args.answer_cdr) && !strcmp(args.answer_cdr, "i")) {
+		/*! \todo We will remove the nocdr stuff for 21 entirely, as part of another review. */
+		return ast_raw_answer(chan);
+	}
+
 	if (!ast_strlen_zero(args.answer_cdr) && !strcasecmp(args.answer_cdr, "nocdr")) {
 		ast_log(AST_LOG_WARNING, "The nocdr option for the Answer application has been removed and is no longer supported.\n");
 	}
@@ -993,7 +1017,6 @@ static int pbx_builtin_execiftime(struct ast_channel *chan, const char *data)
 {
 	char *s, *appname;
 	struct ast_timing timing;
-	struct ast_app *app;
 	static const char * const usage = "ExecIfTime requires an argument:\n  <time range>,<days of week>,<days of month>,<months>[,<timezone>]?<appname>[(<appargs>)]";
 
 	if (ast_strlen_zero(data)) {
@@ -1031,13 +1054,7 @@ static int pbx_builtin_execiftime(struct ast_channel *chan, const char *data)
 			ast_log(LOG_WARNING, "Failed to find closing parenthesis\n");
 	}
 
-
-	if ((app = pbx_findapp(appname))) {
-		return pbx_exec(chan, app, S_OR(s, ""));
-	} else {
-		ast_log(LOG_WARNING, "Cannot locate application %s\n", appname);
-		return -1;
-	}
+	return ast_pbx_exec_application(chan, appname, S_OR(s, ""));
 }
 
 /*!
diff --git a/main/pbx_functions.c b/main/pbx_functions.c
index 08cc191f5faa5ecede5217987b5422f93da8a65e..081c33f6edb590ae35d30dbd5666e8b72ea86f19 100644
--- a/main/pbx_functions.c
+++ b/main/pbx_functions.c
@@ -678,6 +678,7 @@ int ast_func_read2(struct ast_channel *chan, const char *function, struct ast_st
 				ast_str_make_space(str, maxsize);
 			}
 			res = acfptr->read(chan, copy, args, ast_str_buffer(*str), maxsize);
+			ast_str_update(*str); /* Manually set the string length */
 		}
 		if (acfptr->mod && u) {
 			__ast_module_user_remove(acfptr->mod, u);
diff --git a/main/pbx_variables.c b/main/pbx_variables.c
index 6f7439f72f39c97dd5855aa8e05342f076f310c0..b05a9d11ca86da65d91272d9b71c7656d5ba51b5 100644
--- a/main/pbx_variables.c
+++ b/main/pbx_variables.c
@@ -40,6 +40,7 @@
 #include "asterisk/paths.h"
 #include "asterisk/pbx.h"
 #include "asterisk/stasis_channels.h"
+#include "asterisk/test.h"
 #include "pbx_private.h"
 
 /*** DOCUMENTATION
@@ -189,6 +190,8 @@ static const char *ast_str_substring(struct ast_str *value, int offset, int leng
 
 	lr = ast_str_strlen(value); /* compute length after copy, so we never go out of the workspace */
 
+	ast_assert(lr == strlen(ast_str_buffer(value))); /* ast_str_strlen should always agree with strlen */
+
 	/* Quick check if no need to do anything */
 	if (offset == 0 && length >= lr)	/* take the whole string */
 		return ast_str_buffer(value);
@@ -394,7 +397,9 @@ const char *ast_str_retrieve_variable(struct ast_str **str, ssize_t maxlen, stru
 	return ret;
 }
 
-void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *templ, size_t *used)
+void ast_str_substitute_variables_full2(struct ast_str **buf, ssize_t maxlen,
+	struct ast_channel *c, struct varshead *headp, const char *templ,
+	size_t *used, int use_both)
 {
 	/* Substitutes variables into buf, based on string templ */
 	const char *whereweare;
@@ -501,7 +506,8 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str
 
 			/* Store variable name expression to lookup. */
 			ast_str_set_substr(&substr1, 0, vars, len);
-			ast_debug(5, "Evaluating '%s' (from '%s' len %d)\n", ast_str_buffer(substr1), vars, len);
+			ast_debug(5, "Evaluating '%s' (from '%s' len %d)\n",
+				ast_str_buffer(substr1), vars, len);
 
 			/* Substitute if necessary */
 			if (needsub) {
@@ -511,7 +517,8 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str
 						continue;
 					}
 				}
-				ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), NULL);
+				ast_str_substitute_variables_full2(&substr2, 0, c, headp,
+					ast_str_buffer(substr1), NULL, use_both);
 				finalvars = ast_str_buffer(substr2);
 			} else {
 				finalvars = ast_str_buffer(substr1);
@@ -520,31 +527,48 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str
 			parse_variable_name(finalvars, &offset, &offset2, &isfunction);
 			if (isfunction) {
 				/* Evaluate function */
-				if (c || !headp) {
+				res = -1;
+				if (c) {
 					res = ast_func_read2(c, finalvars, &substr3, 0);
-				} else {
+					ast_debug(2, "Function %s result is '%s' from channel\n",
+						finalvars, res ? "" : ast_str_buffer(substr3));
+				}
+				if (!c || (c && res < 0 && use_both)) {
 					struct varshead old;
 					struct ast_channel *bogus;
 
 					bogus = ast_dummy_channel_alloc();
 					if (bogus) {
 						old = *ast_channel_varshead(bogus);
-						*ast_channel_varshead(bogus) = *headp;
+						if (headp) {
+							*ast_channel_varshead(bogus) = *headp;
+						}
 						res = ast_func_read2(bogus, finalvars, &substr3, 0);
 						/* Don't deallocate the varshead that was passed in */
-						*ast_channel_varshead(bogus) = old;
+						if (headp) {
+							*ast_channel_varshead(bogus) = old;
+						}
 						ast_channel_unref(bogus);
 					} else {
 						ast_log(LOG_ERROR, "Unable to allocate bogus channel for function value substitution.\n");
 						res = -1;
 					}
+					ast_debug(2, "Function %s result is '%s' from headp\n",
+						finalvars, res ? "" : ast_str_buffer(substr3));
 				}
-				ast_debug(2, "Function %s result is '%s'\n",
-					finalvars, res ? "" : ast_str_buffer(substr3));
 			} else {
-				/* Retrieve variable value */
-				ast_str_retrieve_variable(&substr3, 0, c, headp, finalvars);
-				res = 0;
+				const char *result;
+				if (c) {
+					result = ast_str_retrieve_variable(&substr3, 0, c, NULL, finalvars);
+					ast_debug(2, "Variable %s result is '%s' from channel\n",
+						finalvars, S_OR(result, ""));
+				}
+				if (!c || (c && !result && use_both)) {
+					result = ast_str_retrieve_variable(&substr3, 0, NULL, headp, finalvars);
+					ast_debug(2, "Variable %s result is '%s' from headp\n",
+						finalvars, S_OR(result, ""));
+				}
+				res = (result ? 0 : -1);
 			}
 			if (!res) {
 				ast_str_substring(substr3, offset, offset2);
@@ -596,7 +620,8 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str
 						continue;
 					}
 				}
-				ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), NULL);
+				ast_str_substitute_variables_full2(&substr2, 0, c, headp,
+					ast_str_buffer(substr1), NULL, use_both);
 				finalvars = ast_str_buffer(substr2);
 			} else {
 				finalvars = ast_str_buffer(substr1);
@@ -616,6 +641,12 @@ void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, str
 	ast_free(substr3);
 }
 
+void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen,
+	struct ast_channel *chan, struct varshead *headp, const char *templ, size_t *used)
+{
+	ast_str_substitute_variables_full2(buf, maxlen, chan, headp, templ, used, 0);
+}
+
 void ast_str_substitute_variables(struct ast_str **buf, ssize_t maxlen, struct ast_channel *chan, const char *templ)
 {
 	ast_str_substitute_variables_full(buf, maxlen, chan, NULL, templ, NULL);
@@ -631,7 +662,7 @@ void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead
 	pbx_substitute_variables_helper_full_location(c, headp, cp1, cp2, count, used, NULL, NULL, 0);
 }
 
-void pbx_substitute_variables_helper_full_location(struct ast_channel *c, struct varshead *headp, const char *cp1, char *cp2, int count, size_t *used, char *context, char *exten, int pri)
+void pbx_substitute_variables_helper_full_location(struct ast_channel *c, struct varshead *headp, const char *cp1, char *cp2, int count, size_t *used, const char *context, const char *exten, int pri)
 {
 	/* Substitutes variables into cp2, based on string cp1, cp2 NO LONGER NEEDS TO BE ZEROED OUT!!!!  */
 	const char *whereweare;
@@ -736,7 +767,7 @@ void pbx_substitute_variables_helper_full_location(struct ast_channel *c, struct
 
 			/* Substitute if necessary */
 			if (needsub) {
-				pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1, NULL);
+				pbx_substitute_variables_helper_full_location(c, headp, var, ltmp, VAR_BUF_SIZE - 1, NULL, context, exten, pri);
 				vars = ltmp;
 			} else {
 				vars = var;
@@ -770,10 +801,13 @@ void pbx_substitute_variables_helper_full_location(struct ast_channel *c, struct
 				/* For dialplan location, if we were told what to substitute explicitly, use that instead */
 				if (exten && !strcmp(vars, "EXTEN")) {
 					ast_copy_string(workspace, exten, VAR_BUF_SIZE);
+					cp4 = workspace;
 				} else if (context && !strcmp(vars, "CONTEXT")) {
 					ast_copy_string(workspace, context, VAR_BUF_SIZE);
+					cp4 = workspace;
 				} else if (pri && !strcmp(vars, "PRIORITY")) {
 					snprintf(workspace, VAR_BUF_SIZE, "%d", pri);
+					cp4 = workspace;
 				} else {
 					pbx_retrieve_variable(c, vars, &cp4, workspace, VAR_BUF_SIZE, headp);
 				}
@@ -829,7 +863,7 @@ void pbx_substitute_variables_helper_full_location(struct ast_channel *c, struct
 
 			/* Substitute if necessary */
 			if (needsub) {
-				pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1, NULL);
+				pbx_substitute_variables_helper_full_location(c, headp, var, ltmp, VAR_BUF_SIZE - 1, NULL, context, exten, pri);
 				vars = ltmp;
 			} else {
 				vars = var;
@@ -924,6 +958,52 @@ static char *handle_show_chanvar(struct ast_cli_entry *e, int cmd, struct ast_cl
 	return CLI_SUCCESS;
 }
 
+/*! \brief CLI support for executing function */
+static char *handle_eval_function(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_channel *c = NULL;
+	char *fn, *substituted;
+	int ret;
+	char workspace[1024];
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "dialplan eval function";
+		e->usage =
+			"Usage: dialplan eval function <name(args)>\n"
+			"       Evaluate a dialplan function call\n"
+			"       A dummy channel is used to evaluate\n"
+			"       the function call, so only global\n"
+			"       variables should be used.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != e->args + 1) {
+		return CLI_SHOWUSAGE;
+	}
+
+	c = ast_dummy_channel_alloc();
+	if (!c) {
+		ast_cli(a->fd, "Unable to allocate bogus channel for function evaluation.\n");
+		return CLI_FAILURE;
+	}
+
+	fn = (char *) a->argv[3];
+	pbx_substitute_variables_helper(c, fn, workspace, sizeof(workspace));
+	substituted = ast_strdupa(workspace);
+	workspace[0] = '\0';
+	ret = ast_func_read(c, substituted, workspace, sizeof(workspace));
+
+	c = ast_channel_unref(c);
+
+	ast_cli(a->fd, "Return Value: %s (%d)\n", ret ? "Failure" : "Success", ret);
+	ast_cli(a->fd, "Result: %s\n", workspace);
+
+	return CLI_SUCCESS;
+}
+
 static char *handle_set_global(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
 	switch (cmd) {
@@ -1215,9 +1295,78 @@ void pbx_builtin_clear_globals(void)
 	ast_rwlock_unlock(&globalslock);
 }
 
+#ifdef TEST_FRAMEWORK
+AST_TEST_DEFINE(test_variable_substrings)
+{
+	int i, res = AST_TEST_PASS;
+	struct ast_channel *chan; /* dummy channel */
+	struct ast_str *str; /* fancy string for holding comparing value */
+
+	const char *test_strings[][5] = {
+		{"somevaluehere", "CALLERID(num):0:25", "somevaluehere"},
+		{"somevaluehere", "CALLERID(num):0:5", "somev"},
+		{"somevaluehere", "CALLERID(num):4:5", "value"},
+		{"somevaluehere", "CALLERID(num):0:-4", "somevalue"},
+		{"somevaluehere", "CALLERID(num):-4", "here"},
+	};
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "variable_substrings";
+		info->category = "/main/pbx/";
+		info->summary = "Test variable substring resolution";
+		info->description = "Verify that variable substrings are calculated correctly";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	if (!(chan = ast_dummy_channel_alloc())) {
+		ast_test_status_update(test, "Unable to allocate dummy channel\n");
+		return AST_TEST_FAIL;
+	}
+
+	if (!(str = ast_str_create(64))) {
+		ast_test_status_update(test, "Unable to allocate dynamic string buffer\n");
+		ast_channel_release(chan);
+		return AST_TEST_FAIL;
+	}
+
+	for (i = 0; i < ARRAY_LEN(test_strings); i++) {
+		char substituted[512], tmp[512] = "";
+
+		ast_set_callerid(chan, test_strings[i][0], NULL, test_strings[i][0]);
+
+		snprintf(tmp, sizeof(tmp), "${%s}", test_strings[i][1]);
+
+		/* test ast_str_substitute_variables */
+		ast_str_substitute_variables(&str, 0, chan, tmp);
+		ast_debug(1, "Comparing STR %s with %s\n", ast_str_buffer(str), test_strings[i][2]);
+		if (strcmp(test_strings[i][2], ast_str_buffer(str))) {
+			ast_test_status_update(test, "Format string '%s' substituted to '%s' using str sub.  Expected '%s'.\n", test_strings[i][0], ast_str_buffer(str), test_strings[i][2]);
+			res = AST_TEST_FAIL;
+		}
+
+		/* test pbx_substitute_variables_helper */
+		pbx_substitute_variables_helper(chan, tmp, substituted, sizeof(substituted) - 1);
+		ast_debug(1, "Comparing PBX %s with %s\n", substituted, test_strings[i][2]);
+		if (strcmp(test_strings[i][2], substituted)) {
+			ast_test_status_update(test, "Format string '%s' substituted to '%s' using pbx sub.  Expected '%s'.\n", test_strings[i][0], substituted, test_strings[i][2]);
+			res = AST_TEST_FAIL;
+		}
+	}
+	ast_free(str);
+
+	ast_channel_release(chan);
+
+	return res;
+}
+#endif
+
 static struct ast_cli_entry vars_cli[] = {
 	AST_CLI_DEFINE(handle_show_globals, "Show global dialplan variables"),
 	AST_CLI_DEFINE(handle_show_chanvar, "Show channel variables"),
+	AST_CLI_DEFINE(handle_eval_function, "Evaluate dialplan function"),
 	AST_CLI_DEFINE(handle_set_global, "Set global dialplan variable"),
 	AST_CLI_DEFINE(handle_set_chanvar, "Set a channel variable"),
 };
@@ -1228,6 +1377,7 @@ static void unload_pbx_variables(void)
 	ast_unregister_application("Set");
 	ast_unregister_application("MSet");
 	pbx_builtin_clear_globals();
+	AST_TEST_UNREGISTER(test_variable_substrings);
 }
 
 int load_pbx_variables(void)
@@ -1238,6 +1388,7 @@ int load_pbx_variables(void)
 	res |= ast_register_application2("Set", pbx_builtin_setvar, NULL, NULL, NULL);
 	res |= ast_register_application2("MSet", pbx_builtin_setvar_multiple, NULL, NULL, NULL);
 	ast_register_cleanup(unload_pbx_variables);
+	AST_TEST_REGISTER(test_variable_substrings);
 
 	return res;
 }
diff --git a/main/rtp_engine.c b/main/rtp_engine.c
index eac3a02ed4e211e302138e248985b46c340f0b1e..d36f70ce05e577849e3a9825b086af3c33f684b6 100644
--- a/main/rtp_engine.c
+++ b/main/rtp_engine.c
@@ -143,7 +143,6 @@
 
 #include "asterisk.h"
 
-#include <math.h>                       /* for sqrt, MAX */
 #include <sched.h>                      /* for sched_yield */
 #include <sys/time.h>                   /* for timeval */
 #include <time.h>                       /* for time_t */
@@ -457,6 +456,28 @@ static void instance_destructor(void *obj)
 
 int ast_rtp_instance_destroy(struct ast_rtp_instance *instance)
 {
+	if (!instance) {
+		return 0;
+	}
+	if (ast_debug_rtp_is_allowed) {
+		char buffer[4][512];
+		ast_debug_rtp(1, "%s:\n"
+			"  RTT:    %s\n"
+			"  Loss:   %s\n"
+			"  Jitter: %s\n"
+			"  MES:    %s\n",
+			instance->channel_uniqueid,
+			ast_rtp_instance_get_quality(instance, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT,
+				buffer[0], sizeof(buffer[0])),
+			ast_rtp_instance_get_quality(instance, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS,
+				buffer[1], sizeof(buffer[1])),
+			ast_rtp_instance_get_quality(instance, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER,
+				buffer[2], sizeof(buffer[2])),
+			ast_rtp_instance_get_quality(instance, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES,
+				buffer[3], sizeof(buffer[3]))
+		);
+	}
+
 	ao2_cleanup(instance);
 
 	return 0;
@@ -2463,6 +2484,8 @@ char *ast_rtp_instance_get_quality(struct ast_rtp_instance *instance, enum ast_r
 		stat = AST_RTP_INSTANCE_STAT_COMBINED_LOSS;
 	} else if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT) {
 		stat = AST_RTP_INSTANCE_STAT_COMBINED_RTT;
+	} else if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES) {
+		stat = AST_RTP_INSTANCE_STAT_COMBINED_MES;
 	} else {
 		return NULL;
 	}
@@ -2474,16 +2497,25 @@ char *ast_rtp_instance_get_quality(struct ast_rtp_instance *instance, enum ast_r
 
 	/* Now actually fill the buffer with the good information */
 	if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY) {
-		snprintf(buf, size, "ssrc=%u;themssrc=%u;lp=%u;rxjitter=%f;rxcount=%u;txjitter=%f;txcount=%u;rlp=%u;rtt=%f",
-			 stats.local_ssrc, stats.remote_ssrc, stats.rxploss, stats.rxjitter, stats.rxcount, stats.txjitter, stats.txcount, stats.txploss, stats.rtt);
+		snprintf(buf, size, "ssrc=%u;themssrc=%u;lp=%u;rxjitter=%f;rxcount=%u;"
+			"txjitter=%f;txcount=%u;rlp=%u;rtt=%f;rxmes=%f;txmes=%f",
+			 stats.local_ssrc, stats.remote_ssrc, stats.rxploss, stats.rxjitter,
+			 stats.rxcount, stats.txjitter, stats.txcount, stats.txploss, stats.rtt,
+			 stats.rxmes, stats.txmes);
 	} else if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER) {
-		snprintf(buf, size, "minrxjitter=%f;maxrxjitter=%f;avgrxjitter=%f;stdevrxjitter=%f;reported_minjitter=%f;reported_maxjitter=%f;reported_avgjitter=%f;reported_stdevjitter=%f;",
-			 stats.local_minjitter, stats.local_maxjitter, stats.local_normdevjitter, sqrt(stats.local_stdevjitter), stats.remote_minjitter, stats.remote_maxjitter, stats.remote_normdevjitter, sqrt(stats.remote_stdevjitter));
+		snprintf(buf, size, "minrxjitter=%010.6f;maxrxjitter=%010.6f;avgrxjitter=%010.6f;stdevrxjitter=%010.6f;mintxjitter=%010.6f;maxtxjitter=%010.6f;avgtxjitter=%010.6f;stdevtxjitter=%010.6f;",
+			 stats.local_minjitter, stats.local_maxjitter, stats.local_normdevjitter, stats.local_stdevjitter, stats.remote_minjitter, stats.remote_maxjitter, stats.remote_normdevjitter, stats.remote_stdevjitter);
 	} else if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS) {
-		snprintf(buf, size, "minrxlost=%f;maxrxlost=%f;avgrxlost=%f;stdevrxlost=%f;reported_minlost=%f;reported_maxlost=%f;reported_avglost=%f;reported_stdevlost=%f;",
-			 stats.local_minrxploss, stats.local_maxrxploss, stats.local_normdevrxploss, sqrt(stats.local_stdevrxploss), stats.remote_minrxploss, stats.remote_maxrxploss, stats.remote_normdevrxploss, sqrt(stats.remote_stdevrxploss));
+		snprintf(buf, size, "  minrxlost=%010.6f;  maxrxlost=%010.6f;  avgrxlost=%010.6f;  stdevrxlost=%010.6f;  mintxlost=%010.6f;  maxtxlost=%010.6f;  avgtxlost=%010.6f;  stdevtxlost=%010.6f;",
+			 stats.local_minrxploss, stats.local_maxrxploss, stats.local_normdevrxploss, stats.local_stdevrxploss, stats.remote_minrxploss, stats.remote_maxrxploss, stats.remote_normdevrxploss, stats.remote_stdevrxploss);
 	} else if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT) {
-		snprintf(buf, size, "minrtt=%f;maxrtt=%f;avgrtt=%f;stdevrtt=%f;", stats.minrtt, stats.maxrtt, stats.normdevrtt, stats.stdevrtt);
+		snprintf(buf, size, "     minrtt=%010.6f;     maxrtt=%010.6f;     avgrtt=%010.6f;     stdevrtt=%010.6f;", stats.minrtt, stats.maxrtt, stats.normdevrtt, stats.stdevrtt);
+	} else if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES) {
+		snprintf(buf, size, "   minrxmes=%010.6f;   maxrxmes=%010.6f;   avgrxmes=%010.6f;   stdevrxmes=%010.6f;   mintxmes=%010.6f;   maxtxmes=%010.6f;   avgtxmes=%010.6f;   stdevtxmes=%010.6f;",
+			 stats.local_minmes, stats.local_maxmes,
+			 stats.local_normdevmes, stats.local_stdevmes,
+			 stats.remote_minmes, stats.remote_maxmes,
+			 stats.remote_normdevmes, stats.remote_stdevmes);
 	}
 
 	return buf;
@@ -2540,6 +2572,15 @@ void ast_rtp_instance_set_stats_vars(struct ast_channel *chan, struct ast_rtp_in
 		}
 	}
 
+	quality = ast_rtp_instance_get_quality(instance,
+		AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES, quality_buf, sizeof(quality_buf));
+	if (quality) {
+		pbx_builtin_setvar_helper(chan, "RTPAUDIOQOSMES", quality);
+		if (bridge) {
+			pbx_builtin_setvar_helper(bridge, "RTPAUDIOQOSMESBRIDGED", quality);
+		}
+	}
+
 	ast_channel_stage_snapshot_done(chan);
 	ast_channel_unlock(chan);
 	if (bridge) {
@@ -3312,6 +3353,7 @@ static struct ast_manager_event_blob *rtcp_report_to_ami(struct stasis_message *
 		struct ast_json *to = ast_json_object_get(payload->blob, "to");
 		struct ast_json *from = ast_json_object_get(payload->blob, "from");
 		struct ast_json *rtt = ast_json_object_get(payload->blob, "rtt");
+		struct ast_json *mes = ast_json_object_get(payload->blob, "mes");
 		if (to) {
 			ast_str_append(&packet_string, 0, "To: %s\r\n", ast_json_string_get(to));
 		}
@@ -3321,6 +3363,9 @@ static struct ast_manager_event_blob *rtcp_report_to_ami(struct stasis_message *
 		if (rtt) {
 			ast_str_append(&packet_string, 0, "RTT: %4.4f\r\n", ast_json_real_get(rtt));
 		}
+		if (mes) {
+			ast_str_append(&packet_string, 0, "MES: %4.1f\r\n", ast_json_real_get(mes));
+		}
 	}
 
 	ast_str_append(&packet_string, 0, "SSRC: 0x%.8x\r\n", ssrc);
@@ -4006,6 +4051,19 @@ struct ast_json *ast_rtp_convert_stats_json(const struct ast_rtp_instance_stats
 	SET_AST_JSON_OBJ(j_res, "normdevrtt", ast_json_real_create(stats->normdevrtt));
 	SET_AST_JSON_OBJ(j_res, "stdevrtt", ast_json_real_create(stats->stdevrtt));
 
+	SET_AST_JSON_OBJ(j_res, "txmes", ast_json_integer_create(stats->txmes));
+	SET_AST_JSON_OBJ(j_res, "rxmes", ast_json_integer_create(stats->rxmes));
+
+	SET_AST_JSON_OBJ(j_res, "remote_maxmes", ast_json_real_create(stats->remote_maxmes));
+	SET_AST_JSON_OBJ(j_res, "remote_minmes", ast_json_real_create(stats->remote_minmes));
+	SET_AST_JSON_OBJ(j_res, "remote_normdevmes", ast_json_real_create(stats->remote_normdevmes));
+	SET_AST_JSON_OBJ(j_res, "remote_stdevmes", ast_json_real_create(stats->remote_stdevmes));
+
+	SET_AST_JSON_OBJ(j_res, "local_maxmes", ast_json_real_create(stats->local_maxmes));
+	SET_AST_JSON_OBJ(j_res, "local_minmes", ast_json_real_create(stats->local_minmes));
+	SET_AST_JSON_OBJ(j_res, "local_normdevmes", ast_json_real_create(stats->local_normdevmes));
+	SET_AST_JSON_OBJ(j_res, "local_stdevmes", ast_json_real_create(stats->local_stdevmes));
+
 	return j_res;
 }
 
diff --git a/main/say.c b/main/say.c
index df9462aa16448a0c3e80fd1e531a81f850420315..14e43dfff539aad4f71fd36e7ee06af12eb6b20f 100644
--- a/main/say.c
+++ b/main/say.c
@@ -160,7 +160,7 @@ struct ast_str* ast_get_character_str(const char *str, const char *lang, enum as
 		}
 		if ((fn && ast_fileexists(fn, NULL, lang) > 0) ||
 			(snprintf(asciibuf + 13, sizeof(asciibuf) - 13, "%d", str[num]) > 0 && ast_fileexists(asciibuf, NULL, lang) > 0 && (fn = asciibuf))) {
-			ast_str_append(&filenames, 0, (num == 0 ? "%s" : "&%s"), fn);
+			ast_str_append(&filenames, 0, "%s%s", ast_str_strlen(filenames) ? "&" : "", fn);
 		}
 		if (upper || lower) {
 			continue;
@@ -189,19 +189,13 @@ static int say_filenames(struct ast_channel *chan, const char *ints, const char
 
 	files = ast_str_buffer(filenames);
 
-	while ((fn = strsep(&files, "&"))) {
+	while (!res && (fn = strsep(&files, "&"))) {
 		res = ast_streamfile(chan, fn, lang);
 		if (!res) {
-			if ((audiofd  > -1) && (ctrlfd > -1))
+			if ((audiofd  > -1) && (ctrlfd > -1)) {
 				res = ast_waitstream_full(chan, ints, audiofd, ctrlfd);
-			else
+			} else {
 				res = ast_waitstream(chan, ints);
-
-			if (res > 0) {
-				/* We were interrupted by a digit */
-				ast_stopstream(chan);
-				ast_free(filenames);
-				return res;
 			}
 		}
 		ast_stopstream(chan);
@@ -288,7 +282,7 @@ struct ast_str* ast_get_phonetic_str(const char *str, const char *lang)
 			fn = fnbuf;
 		}
 		if (fn && ast_fileexists(fn, NULL, lang) > 0) {
-			ast_str_append(&filenames, 0, (num == 0 ? "%s" : "&%s"), fn);
+			ast_str_append(&filenames, 0, "%s%s", ast_str_strlen(filenames) ? "&" : "", fn);
 		}
 		num++;
 	}
@@ -342,7 +336,7 @@ struct ast_str* ast_get_digit_str(const char *str, const char *lang)
 			break;
 		}
 		if (fn && ast_fileexists(fn, NULL, lang) > 0) {
-			ast_str_append(&filenames, 0, (num == 0 ? "%s" : "&%s"), fn);
+			ast_str_append(&filenames, 0, "%s%s", ast_str_strlen(filenames) ? "&" : "", fn);
 		}
 		num++;
 	}
@@ -4933,6 +4927,8 @@ int ast_say_date_with_format_de(struct ast_channel *chan, time_t t, const char *
 				/* 12-Hour */
 				if (tm.tm_hour == 0)
 					ast_copy_string(nextmsg, "digits/12", sizeof(nextmsg));
+				else if (tm.tm_hour == 1)
+					ast_copy_string(nextmsg, "digits/1N", sizeof(nextmsg));
 				else if (tm.tm_hour > 12)
 					snprintf(nextmsg, sizeof(nextmsg), "digits/%d", tm.tm_hour - 12);
 				else
@@ -4945,7 +4941,11 @@ int ast_say_date_with_format_de(struct ast_channel *chan, time_t t, const char *
 			case 'H':
 			case 'k':
 				/* 24-Hour */
-				res = ast_say_number(chan, tm.tm_hour, ints, lang, (char *) NULL);
+				if (tm.tm_hour == 1) {
+					res = wait_file(chan, ints, "digits/1N", lang);
+				} else {
+					res = ast_say_number(chan, tm.tm_hour, ints, lang, (char *) NULL);
+				}
 				if (!res) {
 					res = wait_file(chan, ints, "digits/oclock", lang);
 				}
diff --git a/main/stasis.c b/main/stasis.c
index cf89a9980b16e7579c9d50ccdc6ea99ba977a617..a7cc7e2f1622596cd8d64e32d75e8f3e0adc6e3e 100644
--- a/main/stasis.c
+++ b/main/stasis.c
@@ -2298,7 +2298,7 @@ int stasis_message_type_declined(const char *name)
 	ao2_cleanup(name_in_declined);
 	ao2_ref(cfg, -1);
 	if (res) {
-		ast_log(LOG_NOTICE, "Declining to allocate Stasis message type '%s' due to configuration\n", name);
+		ast_debug(4, "Declining to allocate Stasis message type '%s' due to configuration\n", name);
 	}
 	return res;
 }
diff --git a/main/stasis_channels.c b/main/stasis_channels.c
index 0aff4a28f2db58b5819769517c2486a2bcbb8350..2ebf3fcaf14c13a24032002224c91b2c401ca3c4 100644
--- a/main/stasis_channels.c
+++ b/main/stasis_channels.c
@@ -39,6 +39,7 @@
 #include "asterisk/stasis_channels.h"
 #include "asterisk/dial.h"
 #include "asterisk/linkedlists.h"
+#include "asterisk/utf8.h"
 
 /*** DOCUMENTATION
 	<managerEvent language="en_US" name="VarSet">
@@ -273,7 +274,7 @@ static struct ast_channel_snapshot_base *channel_snapshot_base_create(struct ast
 		return NULL;
 	}
 
-	if (ast_string_field_init(snapshot, 256)) {
+	if (ast_string_field_init(snapshot, 256) || ast_string_field_init_extended(snapshot, protocol_id)) {
 		ao2_ref(snapshot, -1);
 		return NULL;
 	}
@@ -288,6 +289,10 @@ static struct ast_channel_snapshot_base *channel_snapshot_base_create(struct ast
 	snapshot->creationtime = ast_channel_creationtime(chan);
 	snapshot->tech_properties = ast_channel_tech(chan)->properties;
 
+	if (ast_channel_tech(chan)->get_pvt_uniqueid) {
+		ast_string_field_set(snapshot, protocol_id, ast_channel_tech(chan)->get_pvt_uniqueid(chan));
+	}
+
 	return snapshot;
 }
 
@@ -1157,13 +1162,43 @@ void ast_channel_publish_blob(struct ast_channel *chan, struct stasis_message_ty
 void ast_channel_publish_varset(struct ast_channel *chan, const char *name, const char *value)
 {
 	struct ast_json *blob;
+	enum ast_utf8_replace_result result;
+	char *new_value = NULL;
+	size_t new_value_size = 0;
 
 	ast_assert(name != NULL);
 	ast_assert(value != NULL);
 
+	/*
+	 * Call with new-value == NULL to just check for invalid UTF-8
+	 * sequences and get size of buffer needed.
+	 */
+	result = ast_utf8_replace_invalid_chars(new_value, &new_value_size,
+			value, strlen(value));
+
+	if (result == AST_UTF8_REPLACE_VALID) {
+		/*
+		 * If there were no invalid sequences, we can use
+		 * the value directly.
+		 */
+		new_value = (char *)value;
+	} else {
+		/*
+		 * If there were invalid sequences, we need to replace
+		 * them with the UTF-8 U+FFFD replacement character.
+		 */
+		new_value = ast_alloca(new_value_size);
+
+		result = ast_utf8_replace_invalid_chars(new_value, &new_value_size,
+			value, strlen(value));
+
+		ast_log(LOG_WARNING, "%s: The contents of variable '%s' had invalid UTF-8 sequences which were replaced",
+			ast_channel_name(chan), name);
+	}
+
 	blob = ast_json_pack("{s: s, s: s}",
 			     "variable", name,
-			     "value", value);
+			     "value", new_value);
 	if (!blob) {
 		ast_log(LOG_ERROR, "Error creating message\n");
 		return;
@@ -1273,14 +1308,15 @@ struct ast_json *ast_channel_snapshot_to_json(
 	}
 
 	json_chan = ast_json_pack(
-		/* Broken up into groups of three for readability */
-		"{ s: s, s: s, s: s,"
+		/* Broken up into groups for readability */
+		"{ s: s, s: s, s: s, s: s,"
 		"  s: o, s: o, s: s,"
 		"  s: o, s: o, s: s }",
 		/* First line */
 		"id", snapshot->base->uniqueid,
 		"name", snapshot->base->name,
 		"state", ast_state2str(snapshot->state),
+		"protocol_id", snapshot->base->protocol_id,
 		/* Second line */
 		"caller", ast_json_name_number(
 			snapshot->caller->name, snapshot->caller->number),
diff --git a/main/stasis_state.c b/main/stasis_state.c
index 75093a0e1e214424c15835a76d7992a7eade0627..decf228dbeed00a1634d80a033b15bbdd6b41642 100644
--- a/main/stasis_state.c
+++ b/main/stasis_state.c
@@ -113,7 +113,7 @@ static const char *state_id_by_topic(struct stasis_topic *manager_topic,
 	id = strchr(stasis_topic_name(state_topic), '/');
 
 	/* The state's unique id should always exist */
-	ast_assert(id != NULL && (id + 1) != NULL);
+	ast_assert(id != NULL && *(id + 1) != '\0');
 
 	return (id + 1);
 }
diff --git a/main/strings.c b/main/strings.c
index 20769fae1133f1b2b4c48d29068d2699f77ab137..d40eed6a6c9892449ba1ae72696ecb7968c0a6a1 100644
--- a/main/strings.c
+++ b/main/strings.c
@@ -197,7 +197,6 @@ static int str_cmp(void *lhs, void *rhs, int flags)
 	return cmp ? 0 : CMP_MATCH;
 }
 
-//struct ao2_container *ast_str_container_alloc_options(enum ao2_container_opts opts, int buckets)
 struct ao2_container *ast_str_container_alloc_options(enum ao2_alloc_opts opts, int buckets)
 {
 	return ao2_container_alloc_hash(opts, 0, buckets, str_hash, str_sort, str_cmp);
diff --git a/main/stun.c b/main/stun.c
index 8007f3a3ed647c4b23b2f99ae313ab8354302e2e..06e6d9ff2997026c53d1d8729ea528cb757cd508 100644
--- a/main/stun.c
+++ b/main/stun.c
@@ -370,7 +370,13 @@ int ast_stun_handle_packet(int s, struct sockaddr_in *src, unsigned char *data,
 					    st.username ? st.username : "<none>");
 			if (st.username) {
 				append_attr_string(&attr, STUN_USERNAME, st.username, &resplen, &respleft);
-				snprintf(combined, sizeof(combined), "%16s%16s", st.username + 16, st.username);
+				/*
+				 * For Google Voice, the stun username is made up of the local
+				 * and remote usernames, each being fixed at 16 bytes.  We have
+				 * to swap the two at this point.
+				 */
+				snprintf(combined, 17, "%16s", st.username + 16);
+				snprintf(combined + 16, 17, "%16s", st.username);
 			} else {
 				combined[0] = '\0';
 			}
diff --git a/main/tcptls.c b/main/tcptls.c
index b2756d1f8c2b60809687133252fd53f73c9ecd77..dc25cb4047fcc82a262421f185fc052b33820168 100644
--- a/main/tcptls.c
+++ b/main/tcptls.c
@@ -126,7 +126,7 @@ static void write_openssl_error_to_log(void)
 		ast_log(LOG_ERROR, "%.*s\n", (int) length, buffer);
 	}
 
-	ast_free(buffer);
+	ast_std_free(buffer);
 }
 #endif
 
diff --git a/main/test.c b/main/test.c
index 51358032349f151a220263713a0696197e0a5216..7ec505b5b1b8ecddc1af36a359db986b5fc93212 100644
--- a/main/test.c
+++ b/main/test.c
@@ -48,6 +48,16 @@
 #include "asterisk/astobj2.h"
 #include "asterisk/stasis.h"
 #include "asterisk/json.h"
+#include "asterisk/app.h"		/* for ast_replace_sigchld(), etc. */
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <signal.h>
 
 /*! \since 12
  * \brief The topic for test suite messages
@@ -100,6 +110,42 @@ enum test_mode {
 	TEST_NAME_CATEGORY = 2,
 };
 
+#define zfclose(fp) \
+	({ if (fp != NULL) { \
+		fclose(fp); \
+		fp = NULL; \
+	   } \
+	   (void)0; \
+	 })
+
+#define zclose(fd) \
+	({ if (fd != -1) { \
+		close(fd); \
+		fd = -1; \
+	   } \
+	   (void)0; \
+	 })
+
+#define movefd(oldfd, newfd) \
+	({ if (oldfd != newfd) { \
+		dup2(oldfd, newfd); \
+		close(oldfd); \
+		oldfd = -1; \
+	   } \
+	   (void)0; \
+	 })
+
+#define lowerfd(oldfd) \
+	({ int newfd = dup(oldfd); \
+	   if (newfd > oldfd) \
+		close(newfd); \
+	   else { \
+		close(oldfd); \
+		oldfd = newfd; \
+	   } \
+	   (void)0; \
+	 })
+
 /*! List of registered test definitions */
 static AST_LIST_HEAD_STATIC(tests, ast_test);
 
@@ -267,6 +313,220 @@ void ast_test_set_result(struct ast_test *test, enum ast_test_result_state state
 	test->state = state;
 }
 
+void ast_test_capture_init(struct ast_test_capture *capture)
+{
+	capture->outbuf = capture->errbuf = NULL;
+	capture->pid = capture->exitcode = -1;
+}
+
+void ast_test_capture_free(struct ast_test_capture *capture)
+{
+	if (capture) {
+		/*
+		 * Need to use ast_std_free because this memory wasn't
+		 * allocated by the astmm functions.
+		 */
+		ast_std_free(capture->outbuf);
+		capture->outbuf = NULL;
+		ast_std_free(capture->errbuf);
+		capture->errbuf = NULL;
+	}
+	capture->pid = -1;
+	capture->exitcode = -1;
+}
+
+int ast_test_capture_command(struct ast_test_capture *capture, const char *file, char *const argv[], const char *data, unsigned datalen)
+{
+	int fd0[2] = { -1, -1 }, fd1[2] = { -1, -1 }, fd2[2] = { -1, -1 };
+	pid_t pid = -1;
+	int status = 0;
+	FILE *cmd = NULL, *out = NULL, *err = NULL;
+
+	ast_test_capture_init(capture);
+
+	if (data != NULL && datalen > 0) {
+		if (pipe(fd0) == -1) {
+			ast_log(LOG_ERROR, "Couldn't open stdin pipe: %s\n", strerror(errno));
+			goto cleanup;
+		}
+		fcntl(fd0[1], F_SETFL, fcntl(fd0[1], F_GETFL, 0) | O_NONBLOCK);
+	} else {
+		if ((fd0[0] = open("/dev/null", O_RDONLY)) == -1) {
+			ast_log(LOG_ERROR, "Couldn't open /dev/null: %s\n", strerror(errno));
+			goto cleanup;
+		}
+	}
+
+	if (pipe(fd1) == -1) {
+		ast_log(LOG_ERROR, "Couldn't open stdout pipe: %s\n", strerror(errno));
+		goto cleanup;
+	}
+
+	if (pipe(fd2) == -1) {
+		ast_log(LOG_ERROR, "Couldn't open stderr pipe: %s\n", strerror(errno));
+		goto cleanup;
+	}
+
+	/* we don't want anyone else reaping our children */
+	ast_replace_sigchld();
+
+	if ((pid = fork()) == -1) {
+		ast_log(LOG_ERROR, "Failed to fork(): %s\n", strerror(errno));
+		goto cleanup;
+
+	} else if (pid == 0) {
+		fclose(stdin);
+		zclose(fd0[1]);
+		zclose(fd1[0]);
+		zclose(fd2[0]);
+
+		movefd(fd0[0], 0);
+		movefd(fd1[1], 1);
+		movefd(fd2[1], 2);
+
+		execvp(file, argv);
+		ast_log(LOG_ERROR, "Failed to execv(): %s\n", strerror(errno));
+		exit(1);
+
+	} else {
+		char buf[BUFSIZ];
+		int wstatus, n, nfds;
+		fd_set readfds, writefds;
+		unsigned i;
+
+		zclose(fd0[0]);
+		zclose(fd1[1]);
+		zclose(fd2[1]);
+
+		lowerfd(fd0[1]);
+		lowerfd(fd1[0]);
+		lowerfd(fd2[0]);
+
+		if ((cmd = fmemopen(buf, sizeof(buf), "w")) == NULL) {
+			ast_log(LOG_ERROR, "Failed to open memory buffer: %s\n", strerror(errno));
+			kill(pid, SIGKILL);
+			goto cleanup;
+		}
+		for (i = 0; argv[i] != NULL; ++i) {
+			if (i > 0) {
+				fputc(' ', cmd);
+			}
+			fputs(argv[i], cmd);
+		}
+		zfclose(cmd);
+
+		ast_log(LOG_TRACE, "run: %.*s\n", (int)sizeof(buf), buf);
+
+		if ((out = open_memstream(&capture->outbuf, &capture->outlen)) == NULL) {
+			ast_log(LOG_ERROR, "Failed to open output buffer: %s\n", strerror(errno));
+			kill(pid, SIGKILL);
+			goto cleanup;
+		}
+
+		if ((err = open_memstream(&capture->errbuf, &capture->errlen)) == NULL) {
+			ast_log(LOG_ERROR, "Failed to open error buffer: %s\n", strerror(errno));
+			kill(pid, SIGKILL);
+			goto cleanup;
+		}
+
+		while (1) {
+			n = waitpid(pid, &wstatus, WNOHANG);
+
+			if (n == pid && WIFEXITED(wstatus)) {
+				zclose(fd0[1]);
+				zclose(fd1[0]);
+				zclose(fd2[0]);
+				zfclose(out);
+				zfclose(err);
+
+				capture->pid = pid;
+				capture->exitcode = WEXITSTATUS(wstatus);
+
+				ast_log(LOG_TRACE, "run: pid %d exits %d\n", capture->pid, capture->exitcode);
+
+				break;
+			}
+
+			/* a function that does the opposite of ffs()
+			 * would be handy here for finding the highest
+			 * descriptor number.
+			 */
+			nfds = MAX(fd0[1], MAX(fd1[0], fd2[0])) + 1;
+
+			FD_ZERO(&readfds);
+			FD_ZERO(&writefds);
+
+			if (fd0[1] != -1) {
+				if (data != NULL && datalen > 0)
+					FD_SET(fd0[1], &writefds);
+			}
+			if (fd1[0] != -1) {
+				FD_SET(fd1[0], &readfds);
+			}
+			if (fd2[0] != -1) {
+				FD_SET(fd2[0], &readfds);
+			}
+
+			/* not clear that exception fds are meaningful
+			 * with non-network descriptors.
+			 */
+			n = select(nfds, &readfds, &writefds, NULL, NULL);
+
+			/* A version of FD_ISSET() that is tolerant of -1 file descriptors */
+#define SAFE_FD_ISSET(fd, setptr) ((fd) != -1 && FD_ISSET((fd), setptr))
+
+			if (SAFE_FD_ISSET(fd0[1], &writefds)) {
+				n = write(fd0[1], data, datalen);
+				if (n > 0) {
+					data += n;
+					datalen -= MIN(datalen, n);
+					/* out of data, so close stdin */
+					if (datalen == 0)
+						zclose(fd0[1]);
+				} else {
+					zclose(fd0[1]);
+				}
+			}
+
+			if (SAFE_FD_ISSET(fd1[0], &readfds)) {
+				n = read(fd1[0], buf, sizeof(buf));
+				if (n > 0) {
+					fwrite(buf, sizeof(char), n, out);
+				} else {
+					zclose(fd1[0]);
+				}
+			}
+
+			if (SAFE_FD_ISSET(fd2[0], &readfds)) {
+				n = read(fd2[0], buf, sizeof(buf));
+				if (n > 0) {
+					fwrite(buf, sizeof(char), n, err);
+				} else {
+					zclose(fd2[0]);
+				}
+			}
+
+#undef SAFE_FD_ISSET
+		}
+		status = 1;
+
+cleanup:
+		ast_unreplace_sigchld();
+
+		zfclose(cmd);
+		zfclose(out);
+		zfclose(err);
+
+		zclose(fd0[1]);
+		zclose(fd1[0]);
+		zclose(fd1[1]);
+		zclose(fd2[0]);
+		zclose(fd2[1]);
+
+		return status;
+	}
+}
+
 /*
  * These are the Java reserved words we need to munge so Jenkins
  * doesn't barf on them.
@@ -455,8 +715,8 @@ static int test_execute_multiple(const char *name, const char *category, struct
 		/* update total counts as well during this iteration
 		 * even if the current test did not execute this time */
 		last_results.total_time += test->time;
-		last_results.total_tests++;
 		if (test->state != AST_TEST_NOT_RUN) {
+			last_results.total_tests++;
 			if (test->state == AST_TEST_PASS) {
 				last_results.total_passed++;
 			} else {
@@ -533,10 +793,10 @@ static int test_generate_results(const char *name, const char *category, const c
 		 */
 		fprintf(f_xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
 		fprintf(f_xml, "<testsuites>\n");
-		fprintf(f_xml, "\t<testsuite errors=\"0\" time=\"%u.%u\" tests=\"%u\" "
+		fprintf(f_xml, "\t<testsuite errors=\"0\" time=\"%u.%u\" tests=\"%u\" failures=\"%u\" "
 				"name=\"AsteriskUnitTests\">\n",
 				last_results.total_time / 1000, last_results.total_time % 1000,
-				last_results.total_tests);
+				last_results.total_tests, last_results.total_failed);
 		fprintf(f_xml, "\t\t<properties>\n");
 		fprintf(f_xml, "\t\t\t<property name=\"version\" value=\"%s\"/>\n", ast_get_version());
 		fprintf(f_xml, "\t\t</properties>\n");
@@ -1242,3 +1502,4 @@ int ast_test_init(void)
 
 	return 0;
 }
+
diff --git a/main/time.c b/main/time.c
index 266c5cfc0f13c0ca119e94a1887746893f549af6..7babafed96a7e25074eddf3394f5726f7a2a2efc 100644
--- a/main/time.c
+++ b/main/time.c
@@ -25,6 +25,7 @@
 
 #include <inttypes.h>
 #include <string.h>
+#include <strings.h>
 #include <time.h>
 
 #include "asterisk/time.h"
@@ -143,3 +144,31 @@ struct timeval ast_time_create_by_unit_str(unsigned long val, const char *unit)
 {
 	return ast_time_create_by_unit(val, ast_time_str_to_unit(unit));
 }
+
+/*!
+ * \brief Returns a string representation of a time_t as decimal seconds
+ * since the epoch.
+ */
+int ast_time_t_to_string(time_t time, char *buf, size_t length)
+{
+	struct tm tm;
+
+	localtime_r(&time, &tm);
+	return (strftime(buf, length, "%s", &tm) == 0) ? -1 : 0;
+}
+
+/*!
+ * \brief Returns a time_t from a string containing seconds since the epoch.
+ */
+time_t ast_string_to_time_t(const char *str)
+{
+	struct tm tm = { 0, };
+
+	/* handle leading spaces */
+	if (strptime(str, " %s", &tm) == NULL) {
+		return (time_t)-1;
+	}
+	tm.tm_isdst = -1;
+	return mktime(&tm);
+}
+
diff --git a/main/utf8.c b/main/utf8.c
index ab4338ba4c76c2d071e137d1e3c3f479c54b2fbf..00015edd7fd95209f98e3d097e0e9281ef1977e2 100644
--- a/main/utf8.c
+++ b/main/utf8.c
@@ -156,6 +156,154 @@ void ast_utf8_copy_string(char *dst, const char *src, size_t size)
 	*last_good = '\0';
 }
 
+/*!
+ * \warning A UTF-8 sequence could be 1, 2, 3 or 4 bytes long depending
+ * on the first byte in the sequence. Don't try to modify this function
+ * without understanding how UTF-8 works.
+ */
+
+/*
+ * The official unicode replacement character is U+FFFD
+ * which is actually the 3 following bytes:
+ */
+#define REPL_SEQ "\xEF\xBF\xBD"
+#define REPL_SEQ_LEN 3
+
+enum ast_utf8_replace_result
+ast_utf8_replace_invalid_chars(char *dst, size_t *dst_size, const char *src,
+	size_t src_len)
+{
+	enum ast_utf8_replace_result res = AST_UTF8_REPLACE_VALID;
+	size_t src_pos = 0;
+	size_t dst_pos = 0;
+	uint32_t prev_state = UTF8_ACCEPT;
+	uint32_t curr_state = UTF8_ACCEPT;
+	/*
+	* UTF-8 sequences can be 1 - 4 bytes in length so we
+	* have to keep track of where we are.
+	*/
+	int seq_len = 0;
+
+	if (dst) {
+		memset(dst, 0, *dst_size);
+	} else {
+		*dst_size = 0;
+	}
+
+	if (!src || src_len == 0) {
+		return AST_UTF8_REPLACE_VALID;
+	}
+
+	for (prev_state = 0, curr_state = 0; src_pos < src_len; prev_state = curr_state, src_pos++) {
+		uint32_t rc;
+
+		rc = decode(&curr_state, (uint8_t) src[src_pos]);
+
+		if (dst && dst_pos >= *dst_size - 1) {
+			if (prev_state > UTF8_REJECT) {
+				/*
+				 * We ran out of space in the middle of a possible
+				 * multi-byte sequence so we have to back up and
+				 * overwrite the start of the sequence with the
+				 * NULL terminator.
+				 */
+				dst_pos -= (seq_len - (prev_state / 36));
+			}
+			dst[dst_pos] = '\0';
+
+			return AST_UTF8_REPLACE_OVERRUN;
+		}
+
+		if (rc == UTF8_ACCEPT) {
+			if (dst) {
+				dst[dst_pos] = src[src_pos];
+			}
+			dst_pos++;
+			seq_len = 0;
+		}
+
+		if (rc > UTF8_REJECT) {
+			/*
+			 * We're possibly at the start of, or in the middle of,
+			 * a multi-byte sequence. The curr_state will tell us how many
+			 * bytes _should_ be remaining in the sequence.
+			 */
+			if (prev_state == UTF8_ACCEPT) {
+				/* If the previous state was a good character then
+				 * this can only be the start of s sequence
+				 * which is all we care about.
+				 */
+				seq_len = curr_state / 36 + 1;
+			}
+
+			if (dst) {
+				dst[dst_pos] = src[src_pos];
+			}
+			dst_pos++;
+		}
+
+		if (rc == UTF8_REJECT) {
+			/* We got at least 1 rejection so the string is invalid */
+			res = AST_UTF8_REPLACE_INVALID;
+
+			if (prev_state != UTF8_ACCEPT) {
+				/*
+				 * If we were in a multi-byte sequence and this
+				 * byte isn't valid at this time, we'll back
+				 * the destination pointer back to the start
+				 * of the now-invalid sequence and write the
+				 * replacement bytes there.  Then we'll
+				 * process the current byte again in the next
+				 * loop iteration.  It may be quite valid later.
+				 */
+				dst_pos -= (seq_len - (prev_state / 36));
+				src_pos--;
+			}
+			if (dst) {
+				/*
+				 * If we're not just calculating the needed destination
+				 * buffer space, and we don't have enough room to write
+				 * the replacement sequence, terminate the output
+				 * and return.
+				 */
+				if (dst_pos > *dst_size - 4) {
+					dst[dst_pos] = '\0';
+					return AST_UTF8_REPLACE_OVERRUN;
+				}
+				memcpy(&dst[dst_pos], REPL_SEQ, REPL_SEQ_LEN);
+			}
+			dst_pos += REPL_SEQ_LEN;
+			/* Reset the state machine */
+			curr_state = UTF8_ACCEPT;
+		}
+	}
+
+	if (curr_state != UTF8_ACCEPT) {
+		/*
+		 * We were probably in the middle of a
+		 * sequence and ran out of space.
+		 */
+		res = AST_UTF8_INVALID;
+		dst_pos -= (seq_len - (prev_state / 36));
+		if (dst) {
+			if (dst_pos > *dst_size - 4) {
+				dst[dst_pos] = '\0';
+				return AST_UTF8_REPLACE_OVERRUN;
+			}
+			memcpy(&dst[dst_pos], REPL_SEQ, REPL_SEQ_LEN);
+		}
+		dst_pos += REPL_SEQ_LEN;
+	}
+
+	if (dst) {
+		dst[dst_pos] = '\0';
+	} else {
+		*dst_size = dst_pos + 1;
+	}
+
+	return res;
+}
+
 struct ast_utf8_validator {
 	uint32_t state;
 };
@@ -219,6 +367,8 @@ void ast_utf8_validator_destroy(struct ast_utf8_validator *validator)
 
 #ifdef TEST_FRAMEWORK
 
+#include "asterisk/json.h"
+
 AST_TEST_DEFINE(test_utf8_is_valid)
 {
 	switch (cmd) {
@@ -313,6 +463,398 @@ AST_TEST_DEFINE(test_utf8_copy_string)
 	return AST_TEST_PASS;
 }
 
+/*
+ * Let the replace function determine how much
+ * buffer space is required for the destination.
+ */
+#define SIZE_REQUIRED 0
+/*
+ * Set the destination buffer size to the size
+ * we expect it to be.  0xDead has no meaning
+ * other than it's larger than any test needs
+ * a buffer to be.
+ */
+#define SIZE_EXPECTED 0xDead
+
+static int tracs(int run, const char *src, const char *cmp,
+	size_t dst_size, enum ast_utf8_replace_result exp_result)
+{
+	char *dst = NULL;
+	struct ast_json *blob;
+	enum ast_utf8_replace_result result;
+
+	if (dst_size == SIZE_REQUIRED) {
+		ast_utf8_replace_invalid_chars(dst, &dst_size, src, src ? strlen(src) : 0);
+	} else if (dst_size == SIZE_EXPECTED) {
+		dst_size = strlen(cmp) + 1;
+	}
+
+	dst = (char *)ast_alloca(dst_size);
+	result = ast_utf8_replace_invalid_chars(dst, &dst_size, src, src ? strlen(src) : 0);
+	if (result != exp_result || strcmp(dst, cmp) != 0) {
+		ast_log(LOG_ERROR, "Run: %2d Invalid result. Src: '%s', Dst: '%s', ExpDst: '%s'  Result: %d  ExpResult: %d\n",
+			run, src, dst, cmp, result, exp_result);
+		return 0;
+	}
+
+	/*
+	 * The ultimate test: Does jansson accept the result as valid UTF-8?
+	 */
+	blob = ast_json_pack("{s: s, s: s}",
+		"variable", "doesntmatter",
+		"value", dst);
+	ast_json_unref(blob);
+
+	return blob != NULL;
+}
+
+#define ATV(t, v) ast_test_validate(t, v)
+
+AST_TEST_DEFINE(test_utf8_replace_invalid_chars)
+{
+	const char *src;
+	size_t dst_size;
+	enum ast_utf8_replace_result result;
+	int k = 0;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "replace_invalid";
+		info->category = "/main/utf8/";
+		info->summary = "Test ast_utf8_replace_invalid_chars";
+		info->description =
+			"Tests UTF-8 string copying/replacing code.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+/*
+		Table 3-7. Well-Formed UTF-8 Byte Sequences
+		Code Points			First	Second	Third	Fourth
+							Byte	Byte	Byte	Byte
+		U+0000..U+007F		00..7F
+		U+0080..U+07FF		C2..DF	80..BF
+		U+0800..U+0FFF		E0		A0..BF	80..BF
+		U+1000..U+CFFF		E1..EC	80..BF	80..BF
+		U+D000..U+D7FF		ED		80..9F	80..BF
+		U+E000..U+FFFF		EE..EF	80..BF	80..BF
+		U+10000..U+3FFFF	F0		90..BF	80..BF	80..BF
+		U+40000..U+FFFFF	F1..F3	80..BF	80..BF	80..BF
+		U+100000..U+10FFFF	F4		80..8F	80..BF	80..BF
+
+		Older compilers don't support using the \uXXXX or \UXXXXXXXX
+		universal character notation so we have to manually specify
+		the byte sequences even for valid UTF-8 sequences.
+
+		These are the ones used for the tests below:
+
+		\u00B0 = \xC2\xB0
+		\u0800 = \xE0\xA0\x80
+		\uE000 = \xEE\x80\x80
+		\U00040000 = \xF1\x80\x80\x80
+*/
+
+	/*
+	 * Check that NULL destination with a valid source string gives us a
+	 * valid result code and buffer size = the length of the input string
+	 * plus room for the NULL terminator.
+	 */
+	src = "ABC\xC2\xB0xyz";
+	result = ast_utf8_replace_invalid_chars(NULL, &dst_size, src, src ? strlen(src) : 0);
+	ATV(test, result == AST_UTF8_REPLACE_VALID && dst_size == strlen(src) + 1);
+
+	/*
+	 * Check that NULL destination with an invalid source string gives us an
+	 * invalid result code and buffer size = the length of the input string
+	 * plus room for the NULL terminator plus the 2 extra bytes needed for
+	 * the one replacement character.
+	 */
+	src = "ABC\xFFxyz";
+	result = ast_utf8_replace_invalid_chars(NULL, &dst_size, src, src ? strlen(src) : 0);
+	ATV(test, result == AST_UTF8_REPLACE_INVALID && dst_size == strlen(src) + 3);
+
+	/*
+	 * NULL or empty input
+	 */
+	ATV(test, tracs(__LINE__, NULL, "", 80, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "", "", 80, AST_UTF8_REPLACE_VALID));
+
+
+	/* Let the replace function calculate the space needed for result */
+	k = SIZE_REQUIRED;
+
+	/*
+	 * Basic ASCII string
+	 */
+	ATV(test, tracs(__LINE__, "ABC xyzA", "ABC xyzA", k, AST_UTF8_REPLACE_VALID));
+
+	/*
+	 * Mid string.
+	 */
+	/* good single sequences */
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0xyz", "ABC\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80xyz", "ABC\xE0\xA0\x80xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xF1\x80\x80\x80xyz", "ABC\xF1\x80\x80\x80xyz", k, AST_UTF8_REPLACE_VALID));
+	/* good multiple adjacent sequences */
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0\xC2\xB0xyz", "ABC\xC2\xB0\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80\xC2\xB0xyz", "ABC\xE0\xA0\x80\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xF1\x80\x80\x80\xC2\xB0xyz", "ABC\xF1\x80\x80\x80\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	/* Bad sequences */
+	ATV(test, tracs(__LINE__, "ABC\xC2xyz", "ABC\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xC2\xC2xyz", "ABC\xEF\xBF\xBD\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xB0xyz", "ABC\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\xC2xyz", "ABC\xEF\xBF\xBD\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\xF5xyz", "ABC\xEF\xBF\xBD\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0xyz", "ABC\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+
+	/*
+	 * Beginning of string.
+	 */
+	/* good single sequences */
+	ATV(test, tracs(__LINE__, "\xC2\xB0xyz", "\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "\xE0\xA0\x80xyz", "\xE0\xA0\x80xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "\xF1\x80\x80\x80xyz", "\xF1\x80\x80\x80xyz", k, AST_UTF8_REPLACE_VALID));
+	/* good multiple adjacent sequences */
+	ATV(test, tracs(__LINE__, "\xC2\xB0\xC2\xB0xyz", "\xC2\xB0\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "\xE0\xA0\x80\xC2\xB0xyz", "\xE0\xA0\x80\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "\xF1\x80\x80\x80\xC2\xB0xyz", "\xF1\x80\x80\x80\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	/* Bad sequences */
+	ATV(test, tracs(__LINE__, "\xC2xyz", "\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "\xC2\xC2xyz", "\xEF\xBF\xBD\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "\xB0xyz", "\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "\xE0\xA0\xC2xyz", "\xEF\xBF\xBD\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "\xE0\xA0\xF5xyz", "\xEF\xBF\xBD\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "\xE0\xA0xyz", "\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+
+	/*
+	 * End of string.
+	 */
+	/* good single sequences */
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0", "ABC\xC2\xB0", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80", "ABC\xE0\xA0\x80", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xF1\x80\x80\x80", "ABC\xF1\x80\x80\x80", k, AST_UTF8_REPLACE_VALID));
+	/* good multiple adjacent sequences */
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0\xC2\xB0", "ABC\xC2\xB0\xC2\xB0", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80\xC2\xB0", "ABC\xE0\xA0\x80\xC2\xB0", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xF1\x80\x80\x80\xC2\xB0", "ABC\xF1\x80\x80\x80\xC2\xB0", k, AST_UTF8_REPLACE_VALID));
+	/* Bad sequences */
+	ATV(test, tracs(__LINE__, "ABC\xC2", "ABC\xEF\xBF\xBD", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xC2\xC2", "ABC\xEF\xBF\xBD\xEF\xBF\xBD", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xB0", "ABC\xEF\xBF\xBD", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\xC2", "ABC\xEF\xBF\xBD\xEF\xBF\xBD", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\xF5", "ABC\xEF\xBF\xBD\xEF\xBF\xBD", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0", "ABC\xEF\xBF\xBD", k, AST_UTF8_REPLACE_INVALID));
+
+
+	/* Force destination buffer to be only large enough to hold the expected result */
+	k = SIZE_EXPECTED;
+
+	/*
+	 * Mid string.
+	 */
+	/* good single sequences */
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0xyz", "ABC\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80xyz", "ABC\xE0\xA0\x80xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xF1\x80\x80\x80xyz", "ABC\xF1\x80\x80\x80xyz", k, AST_UTF8_REPLACE_VALID));
+	/* good multiple adjacent sequences */
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0\xC2\xB0xyz", "ABC\xC2\xB0\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80\xC2\xB0xyz", "ABC\xE0\xA0\x80\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xF1\x80\x80\x80\xC2\xB0xyz", "ABC\xF1\x80\x80\x80\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	/* Bad sequences */
+	ATV(test, tracs(__LINE__, "ABC\xC2xyz", "ABC\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xC2\xC2xyz", "ABC\xEF\xBF\xBD\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xB0xyz", "ABC\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\xC2xyz", "ABC\xEF\xBF\xBD\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\xF5xyz", "ABC\xEF\xBF\xBD\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0xyz", "ABC\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+
+	/*
+	 * Beginning of string.
+	 */
+	/* good single sequences */
+	ATV(test, tracs(__LINE__, "\xC2\xB0xyz", "\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "\xE0\xA0\x80xyz", "\xE0\xA0\x80xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "\xF1\x80\x80\x80xyz", "\xF1\x80\x80\x80xyz", k, AST_UTF8_REPLACE_VALID));
+	/* good multiple adjacent sequences */
+	ATV(test, tracs(__LINE__, "\xC2\xB0\xC2\xB0xyz", "\xC2\xB0\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "\xE0\xA0\x80\xC2\xB0xyz", "\xE0\xA0\x80\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "\xF1\x80\x80\x80\xC2\xB0xyz", "\xF1\x80\x80\x80\xC2\xB0xyz", k, AST_UTF8_REPLACE_VALID));
+	/* Bad sequences */
+	ATV(test, tracs(__LINE__, "\xC2xyz", "\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "\xC2\xC2xyz", "\xEF\xBF\xBD\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "\xB0xyz", "\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "\xE0\xA0\xC2xyz", "\xEF\xBF\xBD\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "\xE0\xA0\xF5xyz", "\xEF\xBF\xBD\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "\xE0\xA0xyz", "\xEF\xBF\xBDxyz", k, AST_UTF8_REPLACE_INVALID));
+
+	/*
+	 * End of string.
+	 */
+	/* good single sequences */
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0", "ABC\xC2\xB0", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80", "ABC\xE0\xA0\x80", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xF1\x80\x80\x80", "ABC\xF1\x80\x80\x80", k, AST_UTF8_REPLACE_VALID));
+	/* good multiple adjacent sequences */
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0\xC2\xB0", "ABC\xC2\xB0\xC2\xB0", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80\xC2\xB0", "ABC\xE0\xA0\x80\xC2\xB0", k, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xF1\x80\x80\x80\xC2\xB0", "ABC\xF1\x80\x80\x80\xC2\xB0", k, AST_UTF8_REPLACE_VALID));
+	/* Bad sequences */
+	ATV(test, tracs(__LINE__, "ABC\xC2", "ABC\xEF\xBF\xBD", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xC2\xC2", "ABC\xEF\xBF\xBD\xEF\xBF\xBD", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xB0", "ABC\xEF\xBF\xBD", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\xC2", "ABC\xEF\xBF\xBD\xEF\xBF\xBD", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\xF5", "ABC\xEF\xBF\xBD\xEF\xBF\xBD", k, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0", "ABC\xEF\xBF\xBD", k, AST_UTF8_REPLACE_INVALID));
+
+
+	/*
+	 * Overrun Prevention
+	 */
+
+	/* No frills. */
+	k = 9;
+	ATV(test, tracs(__LINE__, "ABC xyzA", "ABC xyzA", k--, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC xyzA", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyzA", "ABC xy", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	/* good single sequences */
+	k = 9;  /* \xC2\xB0 needs 2 bytes */
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0xyz", "ABC\xC2\xB0xyz", k--, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0xyz", "ABC\xC2\xB0xy", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0xyz", "ABC\xC2\xB0x", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0xyz", "ABC\xC2\xB0", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0xyz", "ABC", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0xyz", "ABC", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xC2\xB0xyz", "AB", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	k = 10; /* \xE0\xA0\x80 needs 3 bytes */
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80xyz", "ABC\xE0\xA0\x80xyz", k--, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80xyz", "ABC\xE0\xA0\x80xy", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80xyz", "ABC\xE0\xA0\x80x", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80xyz", "ABC\xE0\xA0\x80", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80xyz", "ABC", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80xyz", "ABC", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80xyz", "ABC", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xE0\xA0\x80xyz", "AB", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	k = 10; /* \xEF\xBF\xBD  needs 3 bytes */
+	ATV(test, tracs(__LINE__, "ABC\xC2xyz", "ABC\xEF\xBF\xBDxyz", k--, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC\xC2xyz", "ABC\xEF\xBF\xBDxy", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xC2xyz", "ABC\xEF\xBF\xBDx", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xC2xyz", "ABC\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xC2xyz", "ABC", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xC2xyz", "ABC", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xC2xyz", "ABC", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC\xC2xyz", "AB", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	k = 14; /* Each \xEF\xBF\xBD needs 3 bytes */
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xFF", "ABC xyz\xEF\xBF\xBD\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xFF", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xFF", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xFF", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xFF", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xFF", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xFF", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xFF", "ABC xy", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xFF", "ABC x", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	/*
+	 * The following tests are classed as "Everything including the kitchen sink".
+	 * Some tests may be redundant.
+	 */
+	k = 11;
+	ATV(test, tracs(__LINE__, "ABC xyz\xFF", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC xyz\xFF", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xFF", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xFF", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xFF", "ABC xy", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	k = 11;
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xB0", "ABC xyz\xC2\xB0", k--, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xB0", "ABC xyz\xC2\xB0", k--, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xB0", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xB0", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2\xB0", "ABC xy", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	k = 11;
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xC2", "ABC xy", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	k = 12;
+	ATV(test, tracs(__LINE__, "ABC xyz\xEE\x80\x80", "ABC xyz\xEE\x80\x80", k--, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC xyz\xEE\x80\x80", "ABC xyz\xEE\x80\x80", k--, AST_UTF8_REPLACE_VALID));
+	ATV(test, tracs(__LINE__, "ABC xyz\xEE\x80\x80", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xEE\x80\x80", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xEE\x80\x80", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xEE\x80\x80", "ABC xy", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	k = 11;
+	ATV(test, tracs(__LINE__, "ABC xyz\xED", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED", "ABC xy", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	k = 14;
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xBF", "ABC xyz\xEF\xBF\xBD\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xBF", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xBF", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xBF", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xBF", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xBF", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xBF", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xBF", "ABC xy", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xBF", "ABC x", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	k = 14;
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xFF", "ABC xyz\xEF\xBF\xBD\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xFF", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xFF", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xFF", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xFF", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xFF", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xFF", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xFF", "ABC xy", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xFF", "ABC x", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	k = 14;
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2", "ABC xyz\xEF\xBF\xBD\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2", "ABC xy", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2", "ABC x", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	k = 14;
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\x80\xC0", "ABC xyz\xEF\xBF\xBD\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\x80\xC0", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\x80\xC0", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\x80\xC0", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\x80\xC0", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\x80\xC0", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\x80\xC0", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\x80\xC0", "ABC xy", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\x80\xC0", "ABC x", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	k = 13;
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2\xB0", "ABC xyz\xEF\xBF\xBD\xC2\xB0", k--, AST_UTF8_REPLACE_INVALID));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2\xB0", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2\xB0", "ABC xyz\xEF\xBF\xBD", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2\xB0", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2\xB0", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2\xB0", "ABC xyz", k--, AST_UTF8_REPLACE_OVERRUN));
+	ATV(test, tracs(__LINE__, "ABC xyz\xED\xC2\xB0", "ABC xy", k--, AST_UTF8_REPLACE_OVERRUN));
+
+	return AST_TEST_PASS;
+}
+
 AST_TEST_DEFINE(test_utf8_validator)
 {
 	struct ast_utf8_validator *validator;
@@ -357,6 +899,7 @@ static void test_utf8_shutdown(void)
 	AST_TEST_UNREGISTER(test_utf8_is_valid);
 	AST_TEST_UNREGISTER(test_utf8_copy_string);
 	AST_TEST_UNREGISTER(test_utf8_validator);
+	AST_TEST_UNREGISTER(test_utf8_replace_invalid_chars);
 }
 
 int ast_utf8_init(void)
@@ -364,6 +907,7 @@ int ast_utf8_init(void)
 	AST_TEST_REGISTER(test_utf8_is_valid);
 	AST_TEST_REGISTER(test_utf8_copy_string);
 	AST_TEST_REGISTER(test_utf8_validator);
+	AST_TEST_REGISTER(test_utf8_replace_invalid_chars);
 
 	ast_register_cleanup(test_utf8_shutdown);
 
diff --git a/main/utils.c b/main/utils.c
index 29676fafc14d11069954868ff815b0260018e510..6111b86dda98a87de87790cfa4fcd4453f5e5c50 100644
--- a/main/utils.c
+++ b/main/utils.c
@@ -1849,7 +1849,74 @@ char *ast_strsep(char **iss, const char sep, uint32_t flags)
 	}
 
 	if (flags & AST_STRSEP_TRIM) {
-		st = ast_strip(st);
+		char *trimmed = ast_strip(st);
+		if (!ast_strlen_zero(trimmed)) {
+			st = trimmed;
+		}
+	}
+
+	if (flags & AST_STRSEP_UNESCAPE) {
+		ast_unescape_quoted(st);
+	}
+
+	return st;
+}
+
+char *ast_strsep_quoted(char **iss, const char sep, const char quote, uint32_t flags)
+{
+	char *st = *iss;
+	char *is;
+	int inquote = 0;
+	int found = 0;
+	char stack[8];
+	const char qstr[] = { quote };
+
+	if (ast_strlen_zero(st)) {
+		return NULL;
+	}
+
+	memset(stack, 0, sizeof(stack));
+
+	for(is = st; *is; is++) {
+		if (*is == '\\') {
+			if (*++is != '\0') {
+				is++;
+			} else {
+				break;
+			}
+		}
+
+		if (*is == quote) {
+			if (*is == stack[inquote]) {
+				stack[inquote--] = '\0';
+			} else {
+				if (++inquote >= sizeof(stack)) {
+					return NULL;
+				}
+				stack[inquote] = *is;
+			}
+		}
+
+		if (*is == sep && !inquote) {
+			*is = '\0';
+			found = 1;
+			*iss = is + 1;
+			break;
+		}
+	}
+	if (!found) {
+		*iss = NULL;
+	}
+
+	if (flags & AST_STRSEP_STRIP) {
+		st = ast_strip_quoted(st, qstr, qstr);
+	}
+
+	if (flags & AST_STRSEP_TRIM) {
+		char *trimmed = ast_strip(st);
+		if (!ast_strlen_zero(trimmed)) {
+			st = trimmed;
+		}
 	}
 
 	if (flags & AST_STRSEP_UNESCAPE) {
@@ -3155,3 +3222,38 @@ int ast_thread_is_user_interface(void)
 
 	return *thread_user_interface;
 }
+
+int ast_check_command_in_path(const char *cmd)
+{
+	char *token, *saveptr, *path = getenv("PATH");
+	char filename[PATH_MAX];
+	int len;
+
+	if (path == NULL) {
+		return 0;
+	}
+
+	path = ast_strdup(path);
+	if (path == NULL) {
+		return 0;
+	}
+
+	token = strtok_r(path, ":", &saveptr);
+	while (token != NULL) {
+		len = snprintf(filename, sizeof(filename), "%s/%s", token, cmd);
+		if (len < 0 || len >= sizeof(filename)) {
+			ast_log(LOG_WARNING, "Path constructed with '%s' too long; skipping\n", token);
+			continue;
+		}
+
+		if (access(filename, X_OK) == 0) {
+			ast_free(path);
+			return 1;
+		}
+
+		token = strtok_r(NULL, ":", &saveptr);
+	}
+	ast_free(path);
+	return 0;
+}
+
diff --git a/main/xml.c b/main/xml.c
index 987f1253995934a2603c3dbf08fc007f542de171..8c49a9efc7c835750c675592d5d07d77bad29c8a 100644
--- a/main/xml.c
+++ b/main/xml.c
@@ -361,6 +361,11 @@ int ast_xml_doc_dump_file(FILE *output, struct ast_xml_doc *doc)
 	return xmlDocDump(output, (xmlDocPtr)doc);
 }
 
+void ast_xml_doc_dump_memory(struct ast_xml_doc *doc, char **buffer, int *length)
+{
+	xmlDocDumpFormatMemory((xmlDocPtr)doc, (xmlChar **)buffer, length, 1);
+}
+
 const char *ast_xml_node_get_name(struct ast_xml_node *node)
 {
 	return (const char *) ((xmlNode *) node)->name;
@@ -539,35 +544,45 @@ struct ast_xml_doc *ast_xslt_apply(struct ast_xslt_doc *axslt, struct ast_xml_do
 {
 	xsltStylesheet *xslt = (xsltStylesheet *)axslt;
 	xmlDoc *xml = (xmlDoc *)axml;
+	xmlDoc *res;
 	xsltTransformContextPtr ctxt;
 	xmlNs *ns;
-	xmlDoc *res;
 	int options = XSLT_PARSE_OPTIONS;
 
 	/*
 	 * Normally we could just call xsltApplyStylesheet() without creating
-	 * our own transform context but we need to pass parameters to it
-	 * that have namespace prefixes and that's not supported.  Instead
-	 * we have to create a transform context, iterate over the namespace
-	 * declarations in the stylesheet (not the incoming xml document),
-	 * and add them to the transform context's xpath context.
+	 * our own transform context but passing parameters to it that have
+	 * namespace prefixes isn't supported. Instead we have to create a
+	 * transform context, iterate over the namespace declarations in the
+	 * stylesheet (not the incoming xml document), add them to the
+	 * transform context's xpath context, and call xsltApplyStylesheetUser.
+	 *
+	 * Since this is a bit involved and libxslt apparently doesn't completely
+	 * clean up after itself in this situation,  we'll only do that dance
+	 * if there are parameters passed in.  Otherwise we just call the simpler
+	 * xsltApplyStylesheet.
 	 *
-	 * The alternative would be to pass the parameters with namespaces
-	 * as text strings but that's not intuitive and results in much
-	 * slower performance than adding the namespaces here.
 	 */
+
+	if (!params) {
+		res = xsltApplyStylesheet(xslt, xml, params);
+		return (struct ast_xml_doc *)res;
+	}
+
 	ctxt = xsltNewTransformContext(xslt, xml);
 	xsltSetCtxtParseOptions(ctxt, options);
 
 	for (ns = xslt->doc->children->nsDef; ns; ns = ns->next) {
 		if (xmlXPathRegisterNs(ctxt->xpathCtxt, ns->prefix, ns->href) != 0) {
-			xmlXPathFreeContext(ctxt->xpathCtxt);
 			xsltFreeTransformContext(ctxt);
 			return NULL;
 		}
 	}
 
 	res = xsltApplyStylesheetUser(xslt, xml, params, NULL, NULL, ctxt);
+	xmlXPathFreeContext(ctxt->xpathCtxt);
+	ctxt->xpathCtxt = NULL;
+	xsltFreeTransformContext(ctxt);
 
 	return (struct ast_xml_doc *)res;
 }
diff --git a/main/xmldoc.c b/main/xmldoc.c
index f924717e9ada3f4877578d574190a6d459387e98..2b75523402a3fea588d4ee270fe7b5a4360712a8 100644
--- a/main/xmldoc.c
+++ b/main/xmldoc.c
@@ -68,9 +68,8 @@ static int xmldoc_parse_specialtags(struct ast_xml_node *fixnode, const char *ta
 /*!
  * \brief Container of documentation trees
  *
- * \note A RWLIST is a sufficient container type to use here for now.
- *       However, some changes will need to be made to implement ref counting
- *       if reload support is added in the future.
+ * \note A RWLIST is a sufficient container type to use, provided
+ *       the list lock is always held while there are references to the list.
  */
 static AST_RWLIST_HEAD_STATIC(xmldoc_tree, documentation_tree);
 
@@ -430,6 +429,8 @@ static int xmldoc_attribute_match(struct ast_xml_node *node, const char *attr, c
  *
  * \retval NULL on error.
  * \retval A node of type ast_xml_node.
+ *
+ * \note Must be called with a RDLOCK held on xmldoc_tree
  */
 static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name, const char *module, const char *language)
 {
@@ -438,7 +439,6 @@ static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name,
 	struct ast_xml_node *lang_match = NULL;
 	struct documentation_tree *doctree;
 
-	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) {
 		/* the core xml documents have priority over thirdparty document. */
 		node = ast_xml_get_root(doctree->doc);
@@ -497,7 +497,6 @@ static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name,
 			break;
 		}
 	}
-	AST_RWLIST_UNLOCK(&xmldoc_tree);
 
 	return node;
 }
@@ -1253,13 +1252,18 @@ static char *_ast_xmldoc_build_syntax(struct ast_xml_node *root_node, const char
 char *ast_xmldoc_build_syntax(const char *type, const char *name, const char *module)
 {
 	struct ast_xml_node *node;
+	char *syntax;
 
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	node = xmldoc_get_node(type, name, module, documentation_language);
 	if (!node) {
+		AST_RWLIST_UNLOCK(&xmldoc_tree);
 		return NULL;
 	}
 
-	return _ast_xmldoc_build_syntax(node, type, name);
+	syntax = _ast_xmldoc_build_syntax(node, type, name);
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
+	return syntax;
 }
 
 /*!
@@ -1705,12 +1709,15 @@ char *ast_xmldoc_build_seealso(const char *type, const char *name, const char *m
 	}
 
 	/* get the application/function root node. */
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	node = xmldoc_get_node(type, name, module, documentation_language);
 	if (!node || !ast_xml_node_get_children(node)) {
+		AST_RWLIST_UNLOCK(&xmldoc_tree);
 		return NULL;
 	}
 
 	output = _ast_xmldoc_build_seealso(node);
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
 
 	return output;
 }
@@ -2077,18 +2084,23 @@ static char *_ast_xmldoc_build_arguments(struct ast_xml_node *node)
 char *ast_xmldoc_build_arguments(const char *type, const char *name, const char *module)
 {
 	struct ast_xml_node *node;
+	char *arguments;
 
 	if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
 		return NULL;
 	}
 
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	node = xmldoc_get_node(type, name, module, documentation_language);
 
 	if (!node || !ast_xml_node_get_children(node)) {
+		AST_RWLIST_UNLOCK(&xmldoc_tree);
 		return NULL;
 	}
 
-	return _ast_xmldoc_build_arguments(node);
+	arguments = _ast_xmldoc_build_arguments(node);
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
+	return arguments;
 }
 
 /*!
@@ -2192,20 +2204,27 @@ static char *_xmldoc_build_field(struct ast_xml_node *node, const char *var, int
 static char *xmldoc_build_field(const char *type, const char *name, const char *module, const char *var, int raw)
 {
 	struct ast_xml_node *node;
+	char *field;
 
 	if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
 		ast_log(LOG_ERROR, "Tried to look in XML tree with faulty values.\n");
 		return NULL;
 	}
 
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	node = xmldoc_get_node(type, name, module, documentation_language);
 
 	if (!node) {
-		ast_log(LOG_WARNING, "Couldn't find %s %s in XML documentation\n", type, name);
+		AST_RWLIST_UNLOCK(&xmldoc_tree);
+		ast_log(LOG_WARNING, "Couldn't find %s %s in XML documentation"
+			" If this module was recently built, run 'xmldoc reload' to refresh documentation\n",
+			type, name);
 		return NULL;
 	}
 
-	return _xmldoc_build_field(node, var, raw);
+	field = _xmldoc_build_field(node, var, raw);
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
+	return field;
 }
 
 /*!
@@ -2465,18 +2484,23 @@ static struct ast_xml_doc_item *xmldoc_build_list_responses(struct ast_xml_node
 struct ast_xml_doc_item *ast_xmldoc_build_list_responses(const char *type, const char *name, const char *module)
 {
 	struct ast_xml_node *node;
+	struct ast_xml_doc_item *responses;
 
 	if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
 		return NULL;
 	}
 
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	node = xmldoc_get_node(type, name, module, documentation_language);
 
 	if (!node || !ast_xml_node_get_children(node)) {
+		AST_RWLIST_UNLOCK(&xmldoc_tree);
 		return NULL;
 	}
 
-	return xmldoc_build_list_responses(node);
+	responses = xmldoc_build_list_responses(node);
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
+	return responses;
 }
 
 /*!
@@ -2530,18 +2554,23 @@ static struct ast_xml_doc_item *xmldoc_build_final_response(struct ast_xml_node
 struct ast_xml_doc_item *ast_xmldoc_build_final_response(const char *type, const char *name, const char *module)
 {
 	struct ast_xml_node *node;
+	static struct ast_xml_doc_item *response;
 
 	if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
 		return NULL;
 	}
 
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	node = xmldoc_get_node(type, name, module, documentation_language);
 
 	if (!node || !ast_xml_node_get_children(node)) {
+		AST_RWLIST_UNLOCK(&xmldoc_tree);
 		return NULL;
 	}
 
-	return xmldoc_build_final_response(node);
+	response = xmldoc_build_final_response(node);
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
+	return response;
 }
 
 struct ast_xml_xpath_results *__attribute__((format(printf, 1, 2))) ast_xmldoc_query(const char *fmt, ...)
@@ -2860,25 +2889,35 @@ static char *handle_dump_docs(struct ast_cli_entry *e, int cmd, struct ast_cli_a
 
 static struct ast_cli_entry cli_dump_xmldocs = AST_CLI_DEFINE(handle_dump_docs, "Dump the XML docs to the specified file");
 
-/*! \brief Close and unload XML documentation. */
-static void xmldoc_unload_documentation(void)
+static char *handle_reload_docs(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static struct ast_cli_entry cli_reload_xmldocs = AST_CLI_DEFINE(handle_reload_docs, "Reload the XML docs");
+
+/*! \note Must be called with xmldoc_tree locked */
+static void xmldoc_purge_documentation(void)
 {
 	struct documentation_tree *doctree;
 
-	ast_cli_unregister(&cli_dump_xmldocs);
-
-	AST_RWLIST_WRLOCK(&xmldoc_tree);
 	while ((doctree = AST_RWLIST_REMOVE_HEAD(&xmldoc_tree, entry))) {
 		ast_free(doctree->filename);
 		ast_xml_close(doctree->doc);
 		ast_free(doctree);
 	}
+}
+
+/*! \brief Close and unload XML documentation. */
+static void xmldoc_unload_documentation(void)
+{
+	ast_cli_unregister(&cli_reload_xmldocs);
+	ast_cli_unregister(&cli_dump_xmldocs);
+
+	AST_RWLIST_WRLOCK(&xmldoc_tree);
+	xmldoc_purge_documentation();
 	AST_RWLIST_UNLOCK(&xmldoc_tree);
 
 	ast_xml_finish();
 }
 
-int ast_xmldoc_load_documentation(void)
+static int xmldoc_load_documentation(int first_time)
 {
 	struct ast_xml_node *root_node;
 	struct ast_xml_doc *tmpdoc;
@@ -2907,12 +2946,14 @@ int ast_xmldoc_load_documentation(void)
 		ast_config_destroy(cfg);
 	}
 
-	/* initialize the XML library. */
-	ast_xml_init();
-
-	ast_cli_register(&cli_dump_xmldocs);
-	/* register function to be run when asterisk finish. */
-	ast_register_cleanup(xmldoc_unload_documentation);
+	if (first_time) {
+		/* initialize the XML library. */
+		ast_xml_init();
+		ast_cli_register(&cli_dump_xmldocs);
+		ast_cli_register(&cli_reload_xmldocs);
+		/* register function to be run when asterisk finish. */
+		ast_register_cleanup(xmldoc_unload_documentation);
+	}
 
 	globbuf.gl_offs = 0;    /* slots to reserve in gl_pathv */
 
@@ -2942,6 +2983,16 @@ int ast_xmldoc_load_documentation(void)
 	ast_free(xmlpattern);
 
 	AST_RWLIST_WRLOCK(&xmldoc_tree);
+
+	if (!first_time) {
+		/* If we're reloading, purge the existing documentation.
+		 * We do this with the lock held so that if somebody
+		 * else tries to get documentation, there's no chance
+		 * of retrieiving it after we purged the old docs
+		 * but before we loaded the new ones. */
+		xmldoc_purge_documentation();
+	}
+
 	/* loop over expanded files */
 	for (i = 0; i < globbuf.gl_pathc; i++) {
 		/* check for duplicates (if we already [try to] open the same file. */
@@ -2993,4 +3044,31 @@ int ast_xmldoc_load_documentation(void)
 	return 0;
 }
 
+int ast_xmldoc_load_documentation(void)
+{
+	return xmldoc_load_documentation(1);
+}
+
+static int xmldoc_reload_documentation(void)
+{
+	return xmldoc_load_documentation(0);
+}
+
+static char *handle_reload_docs(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "xmldoc reload";
+		e->usage =
+			"Usage: xmldoc reload\n"
+			"  Reload XML documentation\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	xmldoc_reload_documentation();
+	return CLI_SUCCESS;
+}
+
 #endif /* AST_XML_DOCS */
diff --git a/makeopts.in b/makeopts.in
index 1cecce846b4902e42d237d90faa5b99c17eb7456..4f3778fa9bdbb77bc961b687864a4a1e7bbace3d 100644
--- a/makeopts.in
+++ b/makeopts.in
@@ -120,6 +120,7 @@ AST_DECLARATION_AFTER_STATEMENT=@AST_DECLARATION_AFTER_STATEMENT@
 AST_TRAMPOLINES=@AST_TRAMPOLINES@
 AST_NO_STRICT_OVERFLOW=@AST_NO_STRICT_OVERFLOW@
 AST_NO_FORMAT_TRUNCATION=@AST_NO_FORMAT_TRUNCATION@
+AST_NO_FORMAT_Y2K=@AST_NO_FORMAT_Y2K@
 AST_NO_STRINGOP_TRUNCATION=@AST_NO_STRINGOP_TRUNCATION@
 AST_SHADOW_WARNINGS=@AST_SHADOW_WARNINGS@
 AST_NESTED_FUNCTIONS=@AST_NESTED_FUNCTIONS@
@@ -203,9 +204,6 @@ LUA_LIB=@LUA_LIB@
 MYSQLCLIENT_INCLUDE=@MYSQLCLIENT_INCLUDE@
 MYSQLCLIENT_LIB=@MYSQLCLIENT_LIB@
 
-NBS_INCLUDE=@NBS_INCLUDE@
-NBS_LIB=@NBS_LIB@
-
 NEON_INCLUDE=@NEON_INCLUDE@
 NEON_LIB=@NEON_LIB@
 NEON29_INCLUDE=@NEON_INCLUDE@
@@ -229,11 +227,6 @@ OPUSFILE_LIB=@OPUSFILE_LIB@
 OSPTK_INCLUDE=@OSPTK_INCLUDE@
 OSPTK_LIB=@OSPTK_LIB@
 
-# ossaudio can optionally use ffmpeg, x11, sdl and sdl_image.
-# Because sdl_image in turn depends on sdl, we don't duplicate the include
-OSS_INCLUDE=@OSS_INCLUDE@ @FFMPEG_INCLUDE@ @SDL_INCLUDE@ @X11_INCLUDE@
-OSS_LIB=@OSS_LIB@ @FFMPEG_LIB@ @SDL_LIB@ @SDL_IMAGE_LIB@ @X11_LIB@
-
 PGSQL_INCLUDE=@PGSQL_INCLUDE@
 PGSQL_LIB=@PGSQL_LIB@
 
@@ -301,9 +294,6 @@ SPEEX_LIB=@SPEEX_LIB@
 SPEEXDSP_INCLUDE=@SPEEXDSP_INCLUDE@
 SPEEXDSP_LIB=@SPEEXDSP_LIB@
 
-SQLITE_INCLUDE=@SQLITE_INCLUDE@
-SQLITE_LIB=@SQLITE_LIB@
-
 SQLITE3_INCLUDE=@SQLITE3_INCLUDE@
 SQLITE3_LIB=@SQLITE3_LIB@
 
@@ -334,24 +324,12 @@ UUID_LIB=@UUID_LIB@
 VORBIS_INCLUDE=@VORBIS_INCLUDE@
 VORBIS_LIB=@VORBIS_LIB@
 
-VPB_INCLUDE=@VPB_INCLUDE@
-VPB_LIB=@VPB_LIB@
-
 HAVE_DAHDI=@PBX_DAHDI@
 DAHDI_INCLUDE=@DAHDI_INCLUDE@
 
 ZLIB_INCLUDE=@ZLIB_INCLUDE@
 ZLIB_LIB=@ZLIB_LIB@
 
-ISDNNET_INCLUDE=@ISDNNET_INCLUDE@
-ISDNNET_LIB=@ISDNNET_LIB@
-
-MISDN_INCLUDE=@MISDN_INCLUDE@
-MISDN_LIB=@MISDN_LIB@
-
-SUPPSERV_INCLUDE=@SUPPSERV_INCLUDE@
-SUPPSERV_LIB=@SUPPSERV_LIB@
-
 CAP_LIB=@CAP_LIB@
 CAP_INCLUDE=@CAP_INCLUDE@
 
@@ -373,3 +351,5 @@ SNDFILE_LIB=@SNDFILE_LIB@
 
 BEANSTALK_INCLUDE=@BEANSTALK_INCLUDE@
 BEANSTALK_LIB=@BEANSTALK_LIB@
+
+HAVE_SBIN_LAUNCHD=@PBX_LAUNCHD@
diff --git a/menuselect/README b/menuselect/README
index 3114e083404d90a7756c6675346a6025e948dfb5..d236168011db91072dc55e5fe6ddf33245feacd5 100644
--- a/menuselect/README
+++ b/menuselect/README
@@ -173,6 +173,6 @@ Conflicts:
 REPORTING BUGS
 
 Any bug reports or feature enhancement submissions to menuselect should be
-submitted at https://issues.asterisk.org/
+submitted at https://github.com/asterisk/asterisk/issues/
 
 Thank you!
diff --git a/menuselect/configure b/menuselect/configure
index 9986b4f9896d1d311758ebb4a534c0f55794cd63..e9abab5d167f22c729211732e1f2a2fcf9f4fc56 100755
--- a/menuselect/configure
+++ b/menuselect/configure
@@ -629,10 +629,9 @@ GTK2_INCLUDE
 PBX_GTK2
 GTK2_LIBS
 GTK2_CFLAGS
-PKG_CONFIG_LIBDIR
-PKG_CONFIG_PATH
-PKG_CONFIG
 CONFIG_LIBXML2
+LIBXML2_LIBS
+LIBXML2_CFLAGS
 SED
 HAVE_NCURSES_SUBDIR
 PBX_TINFO
@@ -660,6 +659,9 @@ EGREP
 GREP
 CPP
 MENUSELECT_DEBUG
+PKG_CONFIG_LIBDIR
+PKG_CONFIG_PATH
+PKG_CONFIG
 GNU_MAKE
 OBJEXT
 EXEEXT
@@ -734,10 +736,12 @@ CFLAGS
 LDFLAGS
 LIBS
 CPPFLAGS
-CPP
 PKG_CONFIG
 PKG_CONFIG_PATH
 PKG_CONFIG_LIBDIR
+CPP
+LIBXML2_CFLAGS
+LIBXML2_LIBS
 GTK2_CFLAGS
 GTK2_LIBS'
 
@@ -1381,12 +1385,16 @@ Some influential environment variables:
   LIBS        libraries to pass to the linker, e.g. -l<library>
   CPPFLAGS    (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
               you have headers in a nonstandard directory <include dir>
-  CPP         C preprocessor
   PKG_CONFIG  path to pkg-config utility
   PKG_CONFIG_PATH
               directories to add to pkg-config's search path
   PKG_CONFIG_LIBDIR
               path overriding pkg-config's built-in search path
+  CPP         C preprocessor
+  LIBXML2_CFLAGS
+              C compiler flags for LIBXML2, overriding pkg-config
+  LIBXML2_LIBS
+              linker flags for LIBXML2, overriding pkg-config
   GTK2_CFLAGS C compiler flags for GTK2, overriding pkg-config
   GTK2_LIBS   linker flags for GTK2, overriding pkg-config
 
@@ -3178,6 +3186,126 @@ GNU_MAKE=$ac_cv_GNU_MAKE
 
 
 
+
+
+
+
+
+
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+	if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
+set dummy ${ac_tool_prefix}pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_PKG_CONFIG+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $PKG_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+PKG_CONFIG=$ac_cv_path_PKG_CONFIG
+if test -n "$PKG_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
+$as_echo "$PKG_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_path_PKG_CONFIG"; then
+  ac_pt_PKG_CONFIG=$PKG_CONFIG
+  # Extract the first word of "pkg-config", so it can be a program name with args.
+set dummy pkg-config; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $ac_pt_PKG_CONFIG in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  ;;
+esac
+fi
+ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG
+if test -n "$ac_pt_PKG_CONFIG"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5
+$as_echo "$ac_pt_PKG_CONFIG" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_pt_PKG_CONFIG" = x; then
+    PKG_CONFIG=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    PKG_CONFIG=$ac_pt_PKG_CONFIG
+  fi
+else
+  PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
+fi
+
+fi
+if test -n "$PKG_CONFIG"; then
+	_pkg_min_version=0.9.0
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5
+$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; }
+	if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+	else
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+		PKG_CONFIG=""
+	fi
+fi
+
 ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
 ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
@@ -4724,6 +4852,94 @@ $as_echo "$ac_cv_path_SED" >&6; }
   rm -f conftest.sed
 
 
+      if test "x${PBX_LIBXML2}" != "x1" -a "${USE_LIBXML2}" != "no"; then
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBXML2" >&5
+$as_echo_n "checking for LIBXML2... " >&6; }
+
+if test -n "$LIBXML2_CFLAGS"; then
+    pkg_cv_LIBXML2_CFLAGS="$LIBXML2_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libxml-2.0\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libxml-2.0") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_LIBXML2_CFLAGS=`$PKG_CONFIG --cflags "libxml-2.0" 2>/dev/null`
+		      test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+if test -n "$LIBXML2_LIBS"; then
+    pkg_cv_LIBXML2_LIBS="$LIBXML2_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libxml-2.0\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libxml-2.0") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_LIBXML2_LIBS=`$PKG_CONFIG --libs "libxml-2.0" 2>/dev/null`
+		      test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi
+        if test $_pkg_short_errors_supported = yes; then
+	        LIBXML2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libxml-2.0" 2>&1`
+        else
+	        LIBXML2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libxml-2.0" 2>&1`
+        fi
+	# Put the nasty error message in config.log where it belongs
+	echo "$LIBXML2_PKG_ERRORS" >&5
+
+
+            PBX_LIBXML2=0
+
+
+elif test $pkg_failed = untried; then
+     	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+            PBX_LIBXML2=0
+
+
+else
+	LIBXML2_CFLAGS=$pkg_cv_LIBXML2_CFLAGS
+	LIBXML2_LIBS=$pkg_cv_LIBXML2_LIBS
+        { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+            PBX_LIBXML2=1
+            LIBXML2_INCLUDE=$(echo ${LIBXML2_CFLAGS} | $SED -e "s|-std=c99||g")
+            LIBXML2_LIB="$LIBXML2_LIBS"
+
+$as_echo "#define HAVE_LIBXML2 1" >>confdefs.h
+
+
+fi
+   fi
+
+
 		if test "x${PBX_LIBXML2}" != "x1" -a "${USE_LIBXML2}" != "no"; then
 		PBX_LIBXML2=0
 		if test -n "$ac_tool_prefix"; then
@@ -4871,127 +5087,7 @@ if test "${PBX_LIBXML2}" != 1; then
 fi
 
 
-
-
-
-
-
-
-if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
-	if test -n "$ac_tool_prefix"; then
-  # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
-set dummy ${ac_tool_prefix}pkg-config; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_PKG_CONFIG+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  case $PKG_CONFIG in
-  [\\/]* | ?:[\\/]*)
-  ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
-  ;;
-  *)
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
-    break 2
-  fi
-done
-  done
-IFS=$as_save_IFS
-
-  ;;
-esac
-fi
-PKG_CONFIG=$ac_cv_path_PKG_CONFIG
-if test -n "$PKG_CONFIG"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
-$as_echo "$PKG_CONFIG" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-
-fi
-if test -z "$ac_cv_path_PKG_CONFIG"; then
-  ac_pt_PKG_CONFIG=$PKG_CONFIG
-  # Extract the first word of "pkg-config", so it can be a program name with args.
-set dummy pkg-config; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  case $ac_pt_PKG_CONFIG in
-  [\\/]* | ?:[\\/]*)
-  ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path.
-  ;;
-  *)
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
-    break 2
-  fi
-done
-  done
-IFS=$as_save_IFS
-
-  ;;
-esac
-fi
-ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG
-if test -n "$ac_pt_PKG_CONFIG"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5
-$as_echo "$ac_pt_PKG_CONFIG" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-  if test "x$ac_pt_PKG_CONFIG" = x; then
-    PKG_CONFIG=""
-  else
-    case $cross_compiling:$ac_tool_warned in
-yes:)
-{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
-$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
-ac_tool_warned=yes ;;
-esac
-    PKG_CONFIG=$ac_pt_PKG_CONFIG
-  fi
-else
-  PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
-fi
-
-fi
-if test -n "$PKG_CONFIG"; then
-	_pkg_min_version=0.9.0
-	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5
-$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; }
-	if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
-		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-	else
-		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-		PKG_CONFIG=""
-	fi
-fi
-
-   if test "x${PBX_GTK2}" != "x1" -a "${USE_GTK2}" != "no"; then
+      if test "x${PBX_GTK2}" != "x1" -a "${USE_GTK2}" != "no"; then
 
 pkg_failed=no
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GTK2" >&5
diff --git a/menuselect/configure.ac b/menuselect/configure.ac
index 4629d52786e6a3d2a266323321a755a449b1b892..589f0828e07f79136bbeae5f581f93a65f2f29ae 100644
--- a/menuselect/configure.ac
+++ b/menuselect/configure.ac
@@ -45,6 +45,7 @@ AH_BOTTOM([#endif])
 AC_PROG_CC
 AST_CHECK_GNU_MAKE
 
+PKG_PROG_PKG_CONFIG
 
 AC_LANG(C)
 AC_ARG_ENABLE([debug],
@@ -90,6 +91,7 @@ else
   AST_EXT_LIB_CHECK([TINFO], [tinfo], [keypad], [curses.h])
 fi
 
+AST_PKG_CONFIG_CHECK([LIBXML2], [libxml-2.0])
 AST_EXT_TOOL_CHECK([LIBXML2], [xml2-config], , ,
         [#include <libxml/tree.h>
         #include <libxml/parser.h>],
diff --git a/menuselect/example_menuselect-tree b/menuselect/example_menuselect-tree
index 83441b1ce9586d39947d87efa3d7ae659c3d2050..0b703e6595872c213a837ea6b874ef3b7437f103 100644
--- a/menuselect/example_menuselect-tree
+++ b/menuselect/example_menuselect-tree
@@ -57,10 +57,6 @@
 		</member>
 		<member name="app_hasnewvoicemail" displayname="Indicator for whether a voice mailbox has messages in a given folder." remove_on_change="apps/app_hasnewvoicemail.o apps/app_hasnewvoicemail.so">
 		</member>
-		<member name="app_ices" displayname="Encode and Stream via icecast and ices" remove_on_change="apps/app_ices.o apps/app_ices.so">
-		</member>
-		<member name="app_image" displayname="Image Transmission Application" remove_on_change="apps/app_image.o apps/app_image.so">
-		</member>
 		<member name="app_ivrdemo" displayname="IVR Demo Application" remove_on_change="apps/app_ivrdemo.o apps/app_ivrdemo.so">
 	<defaultenabled>no</defaultenabled>
 		</member>
@@ -81,8 +77,6 @@
 		</member>
 		<member name="app_mp3" displayname="Silly MP3 Application" remove_on_change="apps/app_mp3.o apps/app_mp3.so">
 		</member>
-		<member name="app_nbscat" displayname="Silly NBS Stream Application" remove_on_change="apps/app_nbscat.o apps/app_nbscat.so">
-		</member>
 		<member name="app_osplookup" displayname="Open Settlement Protocol Applications" remove_on_change="apps/app_osplookup.o apps/app_osplookup.so">
 	<depend>libosptk</depend>
 	<depend>ssl</depend>
@@ -143,8 +137,6 @@
 		</member>
 		<member name="app_transfer" displayname="Transfer" remove_on_change="apps/app_transfer.o apps/app_transfer.so">
 		</member>
-		<member name="app_url" displayname="Send URL Applications" remove_on_change="apps/app_url.o apps/app_url.so">
-		</member>
 		<member name="app_userevent" displayname="Custom User Event Application" remove_on_change="apps/app_userevent.o apps/app_userevent.so">
 		</member>
 		<member name="app_verbose" displayname="Send verbose output" remove_on_change="apps/app_verbose.o apps/app_verbose.so">
@@ -211,24 +203,10 @@
 		<member name="chan_local" displayname="Local Proxy Channel" remove_on_change="channels/chan_local.o channels/chan_local.so">
 		</member>
 		<member name="chan_mgcp" displayname="Media Gateway Control Protocol (MGCP)" remove_on_change="channels/chan_mgcp.o channels/chan_mgcp.so">
-		</member>
-		<member name="chan_misdn" displayname="Channel driver for mISDN Support (Bri/Pri)" remove_on_change="channels/chan_misdn.o channels/chan_misdn.so">
-		</member>
-		<member name="chan_nbs" displayname="Network Broadcast Sound Driver" remove_on_change="channels/chan_nbs.o channels/chan_nbs.so">
-	<depend>nbs</depend>
-		</member>
-		<member name="chan_oss" displayname="OSS Console Channel Driver" remove_on_change="channels/chan_oss.o channels/chan_oss.so">
-	<depend>ossaudio</depend>
-		</member>
-		<member name="chan_phone" displayname="Standard Linux Telephony API Driver" remove_on_change="channels/chan_phone.o channels/chan_phone.so">
-	<depend>ixjuser</depend>
 		</member>
 		<member name="chan_sip" displayname="Session Initiation Protocol (SIP)" remove_on_change="channels/chan_sip.o channels/chan_sip.so">
 		</member>
 		<member name="chan_skinny" displayname="Skinny Client Control Protocol (Skinny)" remove_on_change="channels/chan_skinny.o channels/chan_skinny.so">
-		</member>
-		<member name="chan_vpb" displayname="Standard VoiceTronix API Driver" remove_on_change="channels/chan_vpb.o channels/chan_vpb.so">
-	<depend>vpbapi</depend>
 		</member>
 		<member name="chan_zap" displayname="Zapata Telephony" remove_on_change="channels/chan_zap.o channels/chan_zap.so">
 	<depend>zaptel</depend>
diff --git a/menuselect/menuselect.c b/menuselect/menuselect.c
index 3153ce0f204d1621e503e7bb7a9ea202fd698ba3..54283edabf98a802bdd2ca23b39961f56d2eb9cb 100644
--- a/menuselect/menuselect.c
+++ b/menuselect/menuselect.c
@@ -1130,8 +1130,7 @@ static int build_member_list(void)
 	return res;
 }
 
-/*! \brief Given the string representation of a member and category, mark it as present in a given input file */
-static void mark_as_present(const char *member, const char *category)
+static void mark_as_present_helper(const char *member, const char *category, int present)
 {
 	struct category *cat;
 	struct member *mem;
@@ -1142,31 +1141,44 @@ static void mark_as_present(const char *member, const char *category)
 		negate = 1;
 	}
 
-	print_debug("Marking %s of %s as present\n", member, category);
+	print_debug("Marking %s of %s as %s\n", member, category, present ? "present" : "not present");
 
 	AST_LIST_TRAVERSE(&categories, cat, list) {
-		if (strcmp(category, cat->name))
+		if (strcmp(category, cat->name)) {
 			continue;
+		}
 		AST_LIST_TRAVERSE(&cat->members, mem, list) {
 			if (mem->is_separator) {
 				continue;
 			}
 
 			if (!strcmp(member, mem->name)) {
-				mem->was_enabled = mem->enabled = (negate ? !cat->positive_output : cat->positive_output);
+				if (present) {
+					mem->was_enabled = mem->enabled = (negate ? !cat->positive_output : cat->positive_output);
+				} else {
+					mem->was_enabled = mem->enabled = 0;
+				}
 				print_debug("Just set %s enabled to %d\n", mem->name, mem->enabled);
 				break;
 			}
 		}
-		if (!mem)
+		if (!mem) {
 			fprintf(stderr, "member '%s' in category '%s' not found, ignoring.\n", member, category);
+		}
 		break;
 	}
 
-	if (!cat)
+	if (!cat) {
 		fprintf(stderr, "category '%s' not found! Can't mark '%s' as disabled.\n", category, member);
+	}
 }
 
+/*! \brief Given the string representation of a member and category, mark it as present in a given input file */
+#define mark_as_present(member, category) mark_as_present_helper(member, category, 1)
+
+/*! \brief Given the string representation of a member and category, mark it as not present in a given input file */
+#define mark_as_not_present(member, category) mark_as_present_helper(member, category, 0)
+
 unsigned int enable_member(struct member *mem)
 {
 	struct reference *dep;
@@ -1380,6 +1392,9 @@ static int parse_existing_config(const char *infile)
 	}
 
 	while (fgets(buf, PARSE_BUF_SIZE, f)) {
+		struct category *cat;
+		struct member *mem;
+
 		lineno++;
 
 		if (strlen_zero(buf))
@@ -1414,11 +1429,44 @@ static int parse_existing_config(const char *infile)
 			continue;
 		}
 
-		while ((member = strsep(&parse, " \n"))) {
-			member = skip_blanks(member);
-			if (strlen_zero(member))
+		AST_LIST_TRAVERSE(&categories, cat, list) {
+			if (strcmp(category, cat->name)) {
 				continue;
-			mark_as_present(member, category);
+			}
+			if (!cat->positive_output) {
+				print_debug("Category %s is NOT positive output\n", cat->name);
+				/* if NOT positive_output, then if listed in makeopts, it's disabled! */
+				/* this means that what's listed in menuselect.makeopts is a list of modules
+				 * that are NOT selected, so we can't use that to mark things as present.
+				 * In fact, we need to mark everything as present, UNLESS it's listed
+				* in menuselect.makeopts */
+				AST_LIST_TRAVERSE(&cat->members, mem, list) {
+					if (mem->is_separator) {
+						continue;
+					}
+					mem->was_enabled = 1;
+					print_debug("Just set %s enabled to %d\n", mem->name, mem->enabled);
+				}	
+				/* okay, now go ahead, and mark anything listed in makeopts as NOT present */
+				while ((member = strsep(&parse, " \n"))) {
+					member = skip_blanks(member);
+					if (strlen_zero(member)) {
+						continue;
+					}
+					mark_as_not_present(member, category);
+				}
+			} else {
+				print_debug("Category %s is positive output\n", cat->name);
+				/* if present, it was enabled (e.g. MENUSELECT_CFLAGS, MENUSELECT_UTILS, MENUSELECT_MOH, etc. */
+				while ((member = strsep(&parse, " \n"))) {
+					member = skip_blanks(member);
+					if (strlen_zero(member)) {
+						continue;
+					}
+					mark_as_present(member, category);
+				}
+			}
+			break;
 		}
 	}
 
diff --git a/menuselect/test/menuselect-tree b/menuselect/test/menuselect-tree
index 4c541b50617721fb33f0f4ca22f15286cee384cd..371027874aacebc8511b3de1004fe0b73b9f7062 100644
--- a/menuselect/test/menuselect-tree
+++ b/menuselect/test/menuselect-tree
@@ -28,9 +28,6 @@
 <member name="app_dahdibarge" displayname="Barge in on DAHDI channel application" remove_on_change="apps/app_dahdibarge.o apps/app_dahdibarge.so">
 	<depend name="dahdi">DAHDI</depend>
 </member>
-<member name="app_dahdiras" displayname="DAHDI ISDN Remote Access Server" remove_on_change="apps/app_dahdiras.o apps/app_dahdiras.so">
-	<depend name="dahdi">DAHDI</depend>
-</member>
 <member name="app_dahdiscan" displayname="Scan DAHDI channels application" remove_on_change="apps/app_dahdiscan.o apps/app_dahdiscan.so">
 	<depend name="dahdi">DAHDI</depend>
 </member>
@@ -56,9 +53,6 @@
 </member>
 <member name="app_externalivr" displayname="External IVR Interface Application" remove_on_change="apps/app_externalivr.o apps/app_externalivr.so">
 </member>
-<member name="app_fax" displayname="Simple FAX Application" remove_on_change="apps/app_fax.o apps/app_fax.so">
-	 <depend>spandsp</depend>
-</member>
 <member name="app_festival" displayname="Simple Festival Interface" remove_on_change="apps/app_festival.o apps/app_festival.so">
 </member>
 <member name="app_flash" displayname="Flash channel application" remove_on_change="apps/app_flash.o apps/app_flash.so">
@@ -71,10 +65,6 @@
 </member>
 <member name="app_getcpeid" displayname="Get ADSI CPE ID" remove_on_change="apps/app_getcpeid.o apps/app_getcpeid.so">
 </member>
-<member name="app_ices" displayname="Encode and Stream via icecast and ices" remove_on_change="apps/app_ices.o apps/app_ices.so">
-</member>
-<member name="app_image" displayname="Image Transmission Application" remove_on_change="apps/app_image.o apps/app_image.so">
-</member>
 <member name="app_ivrdemo" displayname="IVR Demo Application" remove_on_change="apps/app_ivrdemo.o apps/app_ivrdemo.so">
 	<defaultenabled>no</defaultenabled>
 </member>
@@ -98,8 +88,6 @@
 </member>
 <member name="app_mp3" displayname="Silly MP3 Application" remove_on_change="apps/app_mp3.o apps/app_mp3.so">
 </member>
-<member name="app_nbscat" displayname="Silly NBS Stream Application" remove_on_change="apps/app_nbscat.o apps/app_nbscat.so">
-</member>
 <member name="app_osplookup" displayname="Open Settlement Protocol Applications" remove_on_change="apps/app_osplookup.o apps/app_osplookup.so">
 	<depend>osptk</depend>
 	<depend>ssl</depend>
@@ -157,8 +145,6 @@
 </member>
 <member name="app_transfer" displayname="Transfers a caller to another extension" remove_on_change="apps/app_transfer.o apps/app_transfer.so">
 </member>
-<member name="app_url" displayname="Send URL Applications" remove_on_change="apps/app_url.o apps/app_url.so">
-</member>
 <member name="app_userevent" displayname="Custom User Event Application" remove_on_change="apps/app_userevent.o apps/app_userevent.so">
 </member>
 <member name="app_verbose" displayname="Send verbose output" remove_on_change="apps/app_verbose.o apps/app_verbose.so">
@@ -248,20 +234,6 @@
 </member>
 <member name="chan_mgcp" displayname="Media Gateway Control Protocol (MGCP)" remove_on_change="channels/chan_mgcp.o channels/chan_mgcp.so">
 </member>
-<member name="chan_misdn" displayname="Channel driver for mISDN Support (BRI/PRI)" remove_on_change="channels/chan_misdn.o channels/chan_misdn.so">
-	<depend>isdnnet</depend>
-	<depend>misdn</depend>
-	<depend>suppserv</depend>
-</member>
-<member name="chan_nbs" displayname="Network Broadcast Sound Support" remove_on_change="channels/chan_nbs.o channels/chan_nbs.so">
-	<depend>nbs</depend>
-</member>
-<member name="chan_oss" displayname="OSS Console Channel Driver" remove_on_change="channels/chan_oss.o channels/chan_oss.so">
-	<depend>ossaudio</depend>
-</member>
-<member name="chan_phone" displayname="Linux Telephony API Support" remove_on_change="channels/chan_phone.o channels/chan_phone.so">
-	<depend>ixjuser</depend>
-</member>
 <member name="chan_sip" displayname="Session Initiation Protocol (SIP)" remove_on_change="channels/chan_sip.o channels/chan_sip.so">
         <depend>chan_local</depend>
 </member>
@@ -274,9 +246,6 @@
 	<depend>usb</depend>
 	<defaultenabled>no</defaultenabled>
 </member>
-<member name="chan_vpb" displayname="Voicetronix API driver" remove_on_change="channels/chan_vpb.oo channels/chan_vpb.so">
-	<depend>vpbapi</depend>
-</member>
 </category>
 <category name="MENUSELECT_CODECS" displayname="Codec Translators" remove_on_change="codecs/modules.link">
 <member name="codec_adpcm" displayname="Adaptive Differential PCM Coder/Decoder" remove_on_change="codecs/codec_adpcm.o codecs/codec_adpcm.so">
@@ -477,9 +446,6 @@
 <member name="res_config_pgsql" displayname="PostgreSQL RealTime Configuration Driver" remove_on_change="res/res_config_pgsql.o res/res_config_pgsql.so">
 	<depend>pgsql</depend>
 </member>
-<member name="res_config_sqlite" displayname="Realtime SQLite configuration" remove_on_change="res/res_config_sqlite.o res/res_config_sqlite.so">
-	<depend>sqlite</depend>
-</member>
 <member name="res_convert" displayname="File format conversion CLI command" remove_on_change="res/res_convert.o res/res_convert.so">
 </member>
 <member name="res_crypto" displayname="Cryptographic Digital Signatures" remove_on_change="res/res_crypto.o res/res_crypto.so">
diff --git a/pbx/dundi-parser.c b/pbx/dundi-parser.c
index bbfc760d092c9ab6dd55566a63ec89881e26381c..5be8f4c301d0c7efff58344827f5486a8d41235e 100644
--- a/pbx/dundi-parser.c
+++ b/pbx/dundi-parser.c
@@ -236,6 +236,8 @@ static char *proto2str(int proto, char *buf, int bufsiz)
 	case DUNDI_PROTO_H323:
 		strncpy(buf, "H.323", bufsiz - 1);
 		break;
+	case DUNDI_PROTO_PJSIP:
+		strncpy(buf, "PJSIP", bufsiz - 1);
 	default:
 		snprintf(buf, bufsiz, "Unknown Proto(%d)", proto);
 	}
diff --git a/pbx/pbx_dundi.c b/pbx/pbx_dundi.c
index 9c0f2faeaf91d27ad42163b2d4c2abcaeae9f7fc..95239d2bca323f39172fe5512d8d975078f8f078 100644
--- a/pbx/pbx_dundi.c
+++ b/pbx/pbx_dundi.c
@@ -208,6 +208,8 @@ static char phone[80];
 static char secretpath[80];
 static char cursecret[80];
 static char ipaddr[80];
+static int outgoing_sip_tech;
+static char pjsip_outgoing_endpoint[80];
 static time_t rotatetime;
 static dundi_eid empty_eid = { { 0, 0, 0, 0, 0, 0 } };
 static int dundi_shutdown = 0;
@@ -388,12 +390,14 @@ static char *tech2str(int tech)
 		return "SIP";
 	case DUNDI_PROTO_H323:
 		return "H323";
+	case DUNDI_PROTO_PJSIP:
+		return "PJSIP";
 	default:
 		return "Unknown";
 	}
 }
 
-static int str2tech(char *str)
+static int str2tech(const char *str)
 {
 	if (!strcasecmp(str, "IAX") || !strcasecmp(str, "IAX2"))
 		return DUNDI_PROTO_IAX;
@@ -401,6 +405,8 @@ static int str2tech(char *str)
 		return DUNDI_PROTO_SIP;
 	else if (!strcasecmp(str, "H323"))
 		return DUNDI_PROTO_H323;
+	else if (!strcasecmp(str, "PJSIP"))
+		return DUNDI_PROTO_PJSIP;
 	else
 		return -1;
 }
@@ -4836,7 +4842,6 @@ static int dundi_exec(struct ast_channel *chan, const char *context, const char
 	int x=0;
 	char req[1024];
 	const char *dundiargs;
-	struct ast_app *dial;
 
 	if (!strncasecmp(context, "macro-", 6)) {
 		if (!chan) {
@@ -4874,13 +4879,42 @@ static int dundi_exec(struct ast_channel *chan, const char *context, const char
 	if (x < res) {
 		/* Got a hit! */
 		dundiargs = pbx_builtin_getvar_helper(chan, "DUNDIDIALARGS");
-		snprintf(req, sizeof(req), "%s/%s,,%s", results[x].tech, results[x].dest,
-			S_OR(dundiargs, ""));
-		dial = pbx_findapp("Dial");
-		if (dial)
-			res = pbx_exec(chan, dial, req);
-	} else
+		/* Backwards compatibility with lookups using chan_sip even if we don't have it anymore:
+		 * At a protocol level, "SIP" will always be specified, but depending on our configuration,
+		 * we will use the user-specified channel driver (from dundi.conf) to complete the call.
+		 */
+		if (!strcasecmp(results[x].tech, "SIP") || !strcasecmp(results[x].tech, "PJSIP")) {
+			/* Only "SIP" is a valid technology for a DUNDi peer to communicate.
+			 * But if they tell use to use "PJSIP" instead, just interpret it as if they said "SIP" instead. */
+			if (strcasecmp(results[x].tech, "SIP")) {
+				ast_log(LOG_WARNING, "%s cannot be specified by DUNDi peers (peer should use SIP for DUNDi lookups instead)\n", results[x].tech);
+			}
+			/* Use whatever we're configured to use for SIP protocol calls. */
+			results[x].techint = outgoing_sip_tech;
+			ast_copy_string(results[x].tech, tech2str(outgoing_sip_tech), sizeof(results[x].tech));
+		}
+		/* PJSIP requires an endpoint to be specified explicitly. */
+		if (outgoing_sip_tech == DUNDI_PROTO_PJSIP) {
+			char *number, *ip = ast_strdupa(results[x].dest);
+			if (ast_strlen_zero(pjsip_outgoing_endpoint)) {
+				ast_log(LOG_WARNING, "PJSIP calls require an endpoint to be specified explicitly (use the pjsip_outgoing_endpoint option in dundi.conf)\n");
+				return -1;
+			}
+			/* Take IP/number and turn it into sip:number@IP */
+			if (ast_strlen_zero(ip)) {
+				ast_log(LOG_WARNING, "PJSIP destination is empty?\n");
+				return -1;
+			}
+			number = strsep(&ip, "/");
+			snprintf(req, sizeof(req), "%s/%s/sip:%s@%s,,%s", results[x].tech, pjsip_outgoing_endpoint, S_OR(number, ""), ip, S_OR(dundiargs, ""));
+			ast_debug(1, "Finalized PJSIP Dial: %s\n", req);
+		} else { /* SIP, or something else. */
+			snprintf(req, sizeof(req), "%s/%s,,%s", results[x].tech, results[x].dest, S_OR(dundiargs, ""));
+		}
+		res = ast_pbx_exec_application(chan, "Dial", req);
+	} else {
 		res = -1;
+	}
 	return res;
 }
 
@@ -4956,6 +4990,7 @@ static int set_config(char *config_file, struct ast_sockaddr *sin, int reload, s
 	dundi_ttl = DUNDI_DEFAULT_TTL;
 	dundi_cache_time = DUNDI_DEFAULT_CACHE_TIME;
 	any_peer = NULL;
+	outgoing_sip_tech = DUNDI_PROTO_SIP;
 
 	AST_LIST_LOCK(&peers);
 
@@ -5026,6 +5061,15 @@ static int set_config(char *config_file, struct ast_sockaddr *sin, int reload, s
 			ast_copy_string(phone, v->value, sizeof(phone));
 		} else if (!strcasecmp(v->name, "storehistory")) {
 			global_storehistory = ast_true(v->value);
+		} else if (!strcasecmp(v->name, "outgoing_sip_tech")) {
+			int outgoing_tech = str2tech(v->value);
+			if (outgoing_tech != DUNDI_PROTO_SIP && outgoing_tech != DUNDI_PROTO_PJSIP) {
+				ast_log(LOG_WARNING, "outgoing_sip_tech must be SIP or PJSIP\n");
+			} else {
+				outgoing_sip_tech = outgoing_tech;
+			}
+		} else if (!strcasecmp(v->name, "pjsip_outgoing_endpoint")) {
+			ast_copy_string(pjsip_outgoing_endpoint, v->value, sizeof(pjsip_outgoing_endpoint));
 		} else if (!strcasecmp(v->name, "cachetime")) {
 			if ((sscanf(v->value, "%30d", &x) == 1)) {
 				dundi_cache_time = x;
diff --git a/pbx/pbx_lua.c b/pbx/pbx_lua.c
index f6c06562b75f31703447fa1cae1feb299a344f3f..67b4317a47dd9b9b99f0d02b8ada18d4f154c3a9 100644
--- a/pbx/pbx_lua.c
+++ b/pbx/pbx_lua.c
@@ -45,8 +45,8 @@
 #include <lauxlib.h>
 #include <lualib.h>
 
-static char *config = "extensions.lua";
-static char *registrar = "pbx_lua";
+static const char *config = "extensions.lua";
+static const char *registrar = "pbx_lua";
 
 #ifdef LOW_MEMORY
 #define LUA_EXT_DATA_SIZE 256
@@ -60,7 +60,7 @@ static char *registrar = "pbx_lua";
  * applications might return */
 #define LUA_GOTO_DETECTED 5
 
-static char *lua_read_extensions_file(lua_State *L, long *size, int *file_not_openable);
+static char *lua_read_extensions_file(lua_State *L, size_t *size, int *file_not_openable);
 static int lua_load_extensions(lua_State *L, struct ast_channel *chan);
 static int lua_reload_extensions(lua_State *L);
 static void lua_free_extensions(void);
@@ -105,7 +105,7 @@ static int exec(struct ast_channel *chan, const char *context, const char *exten
 
 AST_MUTEX_DEFINE_STATIC(config_file_lock);
 static char *config_file_data = NULL;
-static long config_file_size = 0;
+static size_t config_file_size = 0;
 
 static struct ast_context *local_contexts = NULL;
 static struct ast_hashtab *local_table = NULL;
@@ -1093,7 +1093,7 @@ static int lua_extension_cmp(lua_State *L)
  *
  * \return a pointer to the buffer
  */
-static char *lua_read_extensions_file(lua_State *L, long *size, int *file_not_openable)
+static char *lua_read_extensions_file(lua_State *L, size_t *size, int *file_not_openable)
 {
 	FILE *f;
 	int error_func;
@@ -1217,7 +1217,7 @@ static int lua_load_extensions(lua_State *L, struct ast_channel *chan)
  */
 static int lua_reload_extensions(lua_State *L)
 {
-	long size = 0;
+	size_t size = 0;
 	char *data = NULL;
 	int file_not_openable = 0;
 
@@ -1497,7 +1497,8 @@ static int exec(struct ast_channel *chan, const char *context, const char *exten
  */
 static int lua_find_extension(lua_State *L, const char *context, const char *exten, int priority, ast_switch_f *func, int push_func)
 {
-	int context_table, context_order_table, i;
+	int context_table, context_order_table;
+	size_t i;
 
 	ast_debug(2, "Looking up %s@%s:%i\n", exten, context, priority);
 	if (priority != 1)
@@ -1690,7 +1691,7 @@ static int load_module(void)
 		return res;
 
 	if (ast_register_switch(&lua_switch)) {
-		ast_log(LOG_ERROR, "Unable to register LUA PBX switch\n");
+		ast_log(LOG_ERROR, "Unable to register Lua PBX switch\n");
 		return AST_MODULE_LOAD_FAILURE;
 	}
 
diff --git a/res/Makefile b/res/Makefile
index 30914fd2ff2830596c7c3711fd5a572a3b223784..b8756b17538bc8ab9b1d8e503de625b65c588c95 100644
--- a/res/Makefile
+++ b/res/Makefile
@@ -43,13 +43,10 @@ else
 ael/ael_lex.c:
 endif
 	$(ECHO_PREFIX) echo "   [FLEX] $< -> $@"
-	$(CMD_PREFIX) (cd ael; $(FLEX) ael.flex)
-	$(CMD_PREFIX) sed 's@#if __STDC_VERSION__ >= 199901L@#if !defined __STDC_VERSION__ || __STDC_VERSION__ >= 199901L@' $@ > $@.fix
 	$(CMD_PREFIX) echo "#define ASTMM_LIBC ASTMM_REDIRECT" > $@
 	$(CMD_PREFIX) echo "#include \"asterisk.h\"" >> $@
 	$(CMD_PREFIX) echo >> $@
-	$(CMD_PREFIX) cat $@.fix >> $@
-	$(CMD_PREFIX) rm $@.fix
+	$(CMD_PREFIX) $(FLEX) -t ael/ael.flex >> $@
 
 ifneq ($(findstring REBUILD_PARSERS,$(MENUSELECT_CFLAGS)),)
 ael/ael.tab.c ael/ael.tab.h: ael/ael.y
@@ -72,6 +69,11 @@ $(call MOD_ADD_C,res_ari,ari/cli.c ari/config.c ari/ari_websockets.c)
 $(call MOD_ADD_C,res_ari_model,ari/ari_model_validators.c)
 $(call MOD_ADD_C,res_stasis_recording,stasis_recording/stored.c)
 $(call MOD_ADD_C,res_stir_shaken,$(wildcard res_stir_shaken/*.c))
+$(call MOD_ADD_C,res_aeap,$(wildcard res_aeap/*.c))
+$(call MOD_ADD_C,res_geolocation,$(wildcard res_geolocation/*.c))
+
+# These are the xml and xslt files to be embedded
+res_geolocation.so: res_geolocation/pidf_lo_test.o res_geolocation/pidf_to_eprofile.o res_geolocation/eprofile_to_pidf.o
 
 res_parking.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
 snmp/agent.o: _ASTCFLAGS+=-fPIC
@@ -79,3 +81,4 @@ res_snmp.o: _ASTCFLAGS+=-fPIC
 
 # Dependencies for res_ari_*.so are generated, so they're in this file
 include ari.make
+
diff --git a/res/ael/ael.flex b/res/ael/ael.flex
index 82a5f9e17052996e812eb4ec52be9f9825b812b3..64cc9b68a3f4b7487cc3e9962aa7e21ae49cec4c 100644
--- a/res/ael/ael.flex
+++ b/res/ael/ael.flex
@@ -68,9 +68,6 @@
 %option bison-locations
 
 %{
-#define ASTMM_LIBC ASTMM_REDIRECT
-#include "asterisk.h"
-
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
diff --git a/res/ael/ael.tab.c b/res/ael/ael.tab.c
index 802e78f9d66d07ea168338768be41f281b0a431d..b10de2653e82e2af147cf284efe33ce8fd81e2d4 100644
--- a/res/ael/ael.tab.c
+++ b/res/ael/ael.tab.c
@@ -1,19 +1,19 @@
 /* A Bison parser, made by GNU Bison 2.5.  */
 
 /* Bison implementation for Yacc-like parsers in C
-
+   
       Copyright (C) 1984, 1989-1990, 2000-2011 Free Software Foundation, Inc.
-
+   
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
-
+   
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
-
+   
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
@@ -26,7 +26,7 @@
    special exception, which will cause the skeleton and the resulting
    Bison output files to be licensed under the GNU General Public
    License without this special exception.
-
+   
    This special exception was added by the Free Software Foundation in
    version 2.2 of Bison.  */
 
@@ -132,7 +132,7 @@ static char *ael_token_subst(const char *mess);
 
 
 /* Line 268 of yacc.c  */
-#line 137 "ael.tab.c"
+#line 136 "ael.tab.c"
 
 /* Enabling traces.  */
 #ifndef YYDEBUG
@@ -210,7 +210,7 @@ typedef union YYSTYPE
 {
 
 /* Line 293 of yacc.c  */
-#line 59 "ael.y"
+#line 58 "ael.y"
 
 	int	intval;		/* integer value, typically flags */
 	char	*str;		/* strings */
@@ -219,7 +219,7 @@ typedef union YYSTYPE
 
 
 /* Line 293 of yacc.c  */
-#line 224 "ael.tab.c"
+#line 223 "ael.tab.c"
 } YYSTYPE;
 # define YYSTYPE_IS_TRIVIAL 1
 # define yystype YYSTYPE /* obsolescent; will be withdrawn */
@@ -243,7 +243,7 @@ typedef struct YYLTYPE
 /* Copy the second part of user declarations.  */
 
 /* Line 343 of yacc.c  */
-#line 65 "ael.y"
+#line 64 "ael.y"
 
 	/* declaring these AFTER the union makes things a lot simpler! */
 void yyerror(YYLTYPE *locp, struct parse_io *parseio, char const *s);
@@ -266,7 +266,7 @@ static pval *update_last(pval *, YYLTYPE *);
 
 
 /* Line 343 of yacc.c  */
-#line 271 "ael.tab.c"
+#line 270 "ael.tab.c"
 
 #ifdef short
 # undef short
@@ -618,21 +618,21 @@ static const yytype_int8 yyrhs[] =
 /* YYRLINE[YYN] -- source line where rule number YYN was defined.  */
 static const yytype_uint16 yyrline[] =
 {
-       0,   191,   191,   194,   195,   196,   199,   200,   201,   202,
-     205,   206,   209,   218,   219,   220,   221,   222,   225,   231,
-     237,   238,   239,   242,   242,   248,   248,   255,   256,   257,
-     258,   261,   262,   263,   266,   267,   268,   269,   270,   271,
-     272,   273,   274,   277,   282,   286,   294,   299,   304,   313,
-     314,   315,   321,   331,   335,   343,   343,   347,   350,   353,
-     364,   365,   377,   378,   387,   396,   405,   416,   417,   427,
-     440,   441,   450,   461,   470,   473,   474,   475,   478,   481,
-     484,   485,   486,   484,   492,   496,   497,   498,   499,   502,
-     502,   535,   536,   537,   538,   542,   545,   546,   549,   550,
-     553,   556,   560,   564,   568,   574,   575,   579,   582,   588,
-     588,   593,   601,   601,   612,   619,   622,   623,   626,   627,
-     630,   633,   634,   637,   641,   645,   651,   652,   655,   656,
-     657,   663,   668,   673,   674,   675,   686,   689,   690,   697,
-     698,   699,   702,   705
+       0,   190,   190,   193,   194,   195,   198,   199,   200,   201,
+     204,   205,   208,   217,   218,   219,   220,   221,   224,   230,
+     236,   237,   238,   241,   241,   247,   247,   254,   255,   256,
+     257,   260,   261,   262,   265,   266,   267,   268,   269,   270,
+     271,   272,   273,   276,   281,   285,   293,   298,   303,   312,
+     313,   314,   320,   330,   334,   342,   342,   346,   349,   352,
+     363,   364,   376,   377,   386,   395,   404,   415,   416,   426,
+     439,   440,   449,   460,   469,   472,   473,   474,   477,   480,
+     483,   484,   485,   483,   491,   495,   496,   497,   498,   501,
+     501,   534,   535,   536,   537,   541,   544,   545,   548,   549,
+     552,   555,   559,   563,   567,   573,   574,   578,   581,   587,
+     587,   592,   600,   600,   611,   618,   621,   622,   625,   626,
+     629,   632,   633,   636,   640,   644,   650,   651,   654,   655,
+     656,   662,   667,   672,   673,   674,   685,   688,   689,   696,
+     697,   698,   701,   704
 };
 #endif
 
@@ -1497,503 +1497,503 @@ yydestruct (yymsg, yytype, yyvaluep, yylocationp, parseio)
       case 43: /* "word" */
 
 /* Line 1391 of yacc.c  */
-#line 183 "ael.y"
+#line 182 "ael.y"
 	{ free((yyvaluep->str));};
 
 /* Line 1391 of yacc.c  */
-#line 1506 "ael.tab.c"
+#line 1505 "ael.tab.c"
 	break;
       case 46: /* "objects" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1518 "ael.tab.c"
+#line 1517 "ael.tab.c"
 	break;
       case 47: /* "object" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1530 "ael.tab.c"
+#line 1529 "ael.tab.c"
 	break;
       case 48: /* "context_name" */
 
 /* Line 1391 of yacc.c  */
-#line 183 "ael.y"
+#line 182 "ael.y"
 	{ free((yyvaluep->str));};
 
 /* Line 1391 of yacc.c  */
-#line 1539 "ael.tab.c"
+#line 1538 "ael.tab.c"
 	break;
       case 49: /* "context" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1551 "ael.tab.c"
+#line 1550 "ael.tab.c"
 	break;
       case 51: /* "macro" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1563 "ael.tab.c"
+#line 1562 "ael.tab.c"
 	break;
       case 52: /* "globals" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1575 "ael.tab.c"
+#line 1574 "ael.tab.c"
 	break;
       case 53: /* "global_statements" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1587 "ael.tab.c"
+#line 1586 "ael.tab.c"
 	break;
       case 54: /* "assignment" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1599 "ael.tab.c"
+#line 1598 "ael.tab.c"
 	break;
       case 56: /* "local_assignment" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1611 "ael.tab.c"
+#line 1610 "ael.tab.c"
 	break;
       case 58: /* "arglist" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1623 "ael.tab.c"
+#line 1622 "ael.tab.c"
 	break;
       case 59: /* "elements" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1635 "ael.tab.c"
+#line 1634 "ael.tab.c"
 	break;
       case 60: /* "element" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1647 "ael.tab.c"
+#line 1646 "ael.tab.c"
 	break;
       case 61: /* "ignorepat" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1659 "ael.tab.c"
+#line 1658 "ael.tab.c"
 	break;
       case 62: /* "extension" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1671 "ael.tab.c"
+#line 1670 "ael.tab.c"
 	break;
       case 63: /* "statements" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1683 "ael.tab.c"
+#line 1682 "ael.tab.c"
 	break;
       case 64: /* "timerange" */
 
 /* Line 1391 of yacc.c  */
-#line 183 "ael.y"
+#line 182 "ael.y"
 	{ free((yyvaluep->str));};
 
 /* Line 1391 of yacc.c  */
-#line 1692 "ael.tab.c"
+#line 1691 "ael.tab.c"
 	break;
       case 65: /* "timespec" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1704 "ael.tab.c"
+#line 1703 "ael.tab.c"
 	break;
       case 66: /* "test_expr" */
 
 /* Line 1391 of yacc.c  */
-#line 183 "ael.y"
+#line 182 "ael.y"
 	{ free((yyvaluep->str));};
 
 /* Line 1391 of yacc.c  */
-#line 1713 "ael.tab.c"
+#line 1712 "ael.tab.c"
 	break;
       case 68: /* "if_like_head" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1725 "ael.tab.c"
+#line 1724 "ael.tab.c"
 	break;
       case 69: /* "word_list" */
 
 /* Line 1391 of yacc.c  */
-#line 183 "ael.y"
+#line 182 "ael.y"
 	{ free((yyvaluep->str));};
 
 /* Line 1391 of yacc.c  */
-#line 1734 "ael.tab.c"
+#line 1733 "ael.tab.c"
 	break;
       case 71: /* "word3_list" */
 
 /* Line 1391 of yacc.c  */
-#line 183 "ael.y"
+#line 182 "ael.y"
 	{ free((yyvaluep->str));};
 
 /* Line 1391 of yacc.c  */
-#line 1743 "ael.tab.c"
+#line 1742 "ael.tab.c"
 	break;
       case 72: /* "goto_word" */
 
 /* Line 1391 of yacc.c  */
-#line 183 "ael.y"
+#line 182 "ael.y"
 	{ free((yyvaluep->str));};
 
 /* Line 1391 of yacc.c  */
-#line 1752 "ael.tab.c"
+#line 1751 "ael.tab.c"
 	break;
       case 73: /* "switch_statement" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1764 "ael.tab.c"
+#line 1763 "ael.tab.c"
 	break;
       case 74: /* "statement" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1776 "ael.tab.c"
+#line 1775 "ael.tab.c"
 	break;
       case 79: /* "opt_else" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1788 "ael.tab.c"
+#line 1787 "ael.tab.c"
 	break;
       case 80: /* "target" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1800 "ael.tab.c"
+#line 1799 "ael.tab.c"
 	break;
       case 81: /* "opt_pri" */
 
 /* Line 1391 of yacc.c  */
-#line 183 "ael.y"
+#line 182 "ael.y"
 	{ free((yyvaluep->str));};
 
 /* Line 1391 of yacc.c  */
-#line 1809 "ael.tab.c"
+#line 1808 "ael.tab.c"
 	break;
       case 82: /* "jumptarget" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1821 "ael.tab.c"
+#line 1820 "ael.tab.c"
 	break;
       case 83: /* "macro_call" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1833 "ael.tab.c"
+#line 1832 "ael.tab.c"
 	break;
       case 85: /* "application_call_head" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1845 "ael.tab.c"
+#line 1844 "ael.tab.c"
 	break;
       case 87: /* "application_call" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1857 "ael.tab.c"
+#line 1856 "ael.tab.c"
 	break;
       case 88: /* "opt_word" */
 
 /* Line 1391 of yacc.c  */
-#line 183 "ael.y"
+#line 182 "ael.y"
 	{ free((yyvaluep->str));};
 
 /* Line 1391 of yacc.c  */
-#line 1866 "ael.tab.c"
+#line 1865 "ael.tab.c"
 	break;
       case 89: /* "eval_arglist" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1878 "ael.tab.c"
+#line 1877 "ael.tab.c"
 	break;
       case 90: /* "case_statements" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1890 "ael.tab.c"
+#line 1889 "ael.tab.c"
 	break;
       case 91: /* "case_statement" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1902 "ael.tab.c"
+#line 1901 "ael.tab.c"
 	break;
       case 92: /* "macro_statements" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1914 "ael.tab.c"
+#line 1913 "ael.tab.c"
 	break;
       case 93: /* "macro_statement" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1926 "ael.tab.c"
+#line 1925 "ael.tab.c"
 	break;
       case 94: /* "switches" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1938 "ael.tab.c"
+#line 1937 "ael.tab.c"
 	break;
       case 95: /* "eswitches" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1950 "ael.tab.c"
+#line 1949 "ael.tab.c"
 	break;
       case 96: /* "switchlist" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1962 "ael.tab.c"
+#line 1961 "ael.tab.c"
 	break;
       case 97: /* "included_entry" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1974 "ael.tab.c"
+#line 1973 "ael.tab.c"
 	break;
       case 98: /* "includeslist" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1986 "ael.tab.c"
+#line 1985 "ael.tab.c"
 	break;
       case 99: /* "includes" */
 
 /* Line 1391 of yacc.c  */
-#line 170 "ael.y"
+#line 169 "ael.y"
 	{
 		destroy_pval((yyvaluep->pval));
 		prev_word=0;
 	};
 
 /* Line 1391 of yacc.c  */
-#line 1998 "ael.tab.c"
+#line 1997 "ael.tab.c"
 	break;
 
       default:
@@ -2322,77 +2322,77 @@ yyreduce:
         case 2:
 
 /* Line 1806 of yacc.c  */
-#line 191 "ael.y"
+#line 190 "ael.y"
     { (yyval.pval) = parseio->pval = (yyvsp[(1) - (1)].pval); }
     break;
 
   case 3:
 
 /* Line 1806 of yacc.c  */
-#line 194 "ael.y"
+#line 193 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (1)].pval);}
     break;
 
   case 4:
 
 /* Line 1806 of yacc.c  */
-#line 195 "ael.y"
+#line 194 "ael.y"
     { (yyval.pval) = linku1((yyvsp[(1) - (2)].pval), (yyvsp[(2) - (2)].pval)); }
     break;
 
   case 5:
 
 /* Line 1806 of yacc.c  */
-#line 196 "ael.y"
+#line 195 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (2)].pval);}
     break;
 
   case 6:
 
 /* Line 1806 of yacc.c  */
-#line 199 "ael.y"
+#line 198 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (1)].pval);}
     break;
 
   case 7:
 
 /* Line 1806 of yacc.c  */
-#line 200 "ael.y"
+#line 199 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (1)].pval);}
     break;
 
   case 8:
 
 /* Line 1806 of yacc.c  */
-#line 201 "ael.y"
+#line 200 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (1)].pval);}
     break;
 
   case 9:
 
 /* Line 1806 of yacc.c  */
-#line 202 "ael.y"
+#line 201 "ael.y"
     {(yyval.pval)=0;/* allow older docs to be read */}
     break;
 
   case 10:
 
 /* Line 1806 of yacc.c  */
-#line 205 "ael.y"
+#line 204 "ael.y"
     { (yyval.str) = (yyvsp[(1) - (1)].str); }
     break;
 
   case 11:
 
 /* Line 1806 of yacc.c  */
-#line 206 "ael.y"
+#line 205 "ael.y"
     { (yyval.str) = strdup("default"); }
     break;
 
   case 12:
 
 /* Line 1806 of yacc.c  */
-#line 209 "ael.y"
+#line 208 "ael.y"
     {
 		(yyval.pval) = npval2(PV_CONTEXT, &(yylsp[(1) - (6)]), &(yylsp[(6) - (6)]));
 		(yyval.pval)->u1.str = (yyvsp[(3) - (6)].str);
@@ -2404,42 +2404,42 @@ yyreduce:
   case 13:
 
 /* Line 1806 of yacc.c  */
-#line 218 "ael.y"
+#line 217 "ael.y"
     { (yyval.intval) = 1; }
     break;
 
   case 14:
 
 /* Line 1806 of yacc.c  */
-#line 219 "ael.y"
+#line 218 "ael.y"
     { (yyval.intval) = 0; }
     break;
 
   case 15:
 
 /* Line 1806 of yacc.c  */
-#line 220 "ael.y"
+#line 219 "ael.y"
     { (yyval.intval) = 2; }
     break;
 
   case 16:
 
 /* Line 1806 of yacc.c  */
-#line 221 "ael.y"
+#line 220 "ael.y"
     { (yyval.intval)=3; }
     break;
 
   case 17:
 
 /* Line 1806 of yacc.c  */
-#line 222 "ael.y"
+#line 221 "ael.y"
     { (yyval.intval)=3; }
     break;
 
   case 18:
 
 /* Line 1806 of yacc.c  */
-#line 225 "ael.y"
+#line 224 "ael.y"
     {
 		(yyval.pval) = npval2(PV_MACRO, &(yylsp[(1) - (8)]), &(yylsp[(8) - (8)]));
 		(yyval.pval)->u1.str = (yyvsp[(2) - (8)].str); (yyval.pval)->u2.arglist = (yyvsp[(4) - (8)].pval); (yyval.pval)->u3.macro_statements = (yyvsp[(7) - (8)].pval);
@@ -2449,7 +2449,7 @@ yyreduce:
   case 19:
 
 /* Line 1806 of yacc.c  */
-#line 231 "ael.y"
+#line 230 "ael.y"
     {
 		(yyval.pval) = npval2(PV_GLOBALS, &(yylsp[(1) - (4)]), &(yylsp[(4) - (4)]));
 		(yyval.pval)->u1.statements = (yyvsp[(3) - (4)].pval);
@@ -2459,35 +2459,35 @@ yyreduce:
   case 20:
 
 /* Line 1806 of yacc.c  */
-#line 237 "ael.y"
+#line 236 "ael.y"
     { (yyval.pval) = NULL; }
     break;
 
   case 21:
 
 /* Line 1806 of yacc.c  */
-#line 238 "ael.y"
+#line 237 "ael.y"
     {(yyval.pval) = linku1((yyvsp[(1) - (2)].pval), (yyvsp[(2) - (2)].pval)); }
     break;
 
   case 22:
 
 /* Line 1806 of yacc.c  */
-#line 239 "ael.y"
+#line 238 "ael.y"
     {(yyval.pval)=(yyvsp[(2) - (2)].pval);}
     break;
 
   case 23:
 
 /* Line 1806 of yacc.c  */
-#line 242 "ael.y"
+#line 241 "ael.y"
     { reset_semicount(parseio->scanner); }
     break;
 
   case 24:
 
 /* Line 1806 of yacc.c  */
-#line 242 "ael.y"
+#line 241 "ael.y"
     {
 		(yyval.pval) = npval2(PV_VARDEC, &(yylsp[(1) - (5)]), &(yylsp[(5) - (5)]));
 		(yyval.pval)->u1.str = (yyvsp[(1) - (5)].str);
@@ -2497,14 +2497,14 @@ yyreduce:
   case 25:
 
 /* Line 1806 of yacc.c  */
-#line 248 "ael.y"
+#line 247 "ael.y"
     { reset_semicount(parseio->scanner); }
     break;
 
   case 26:
 
 /* Line 1806 of yacc.c  */
-#line 248 "ael.y"
+#line 247 "ael.y"
     {
 		(yyval.pval) = npval2(PV_LOCALVARDEC, &(yylsp[(1) - (6)]), &(yylsp[(6) - (6)]));
 		(yyval.pval)->u1.str = (yyvsp[(2) - (6)].str);
@@ -2514,119 +2514,119 @@ yyreduce:
   case 27:
 
 /* Line 1806 of yacc.c  */
-#line 255 "ael.y"
+#line 254 "ael.y"
     { (yyval.pval) = NULL; }
     break;
 
   case 28:
 
 /* Line 1806 of yacc.c  */
-#line 256 "ael.y"
+#line 255 "ael.y"
     { (yyval.pval) = nword((yyvsp[(1) - (1)].str), &(yylsp[(1) - (1)])); }
     break;
 
   case 29:
 
 /* Line 1806 of yacc.c  */
-#line 257 "ael.y"
+#line 256 "ael.y"
     { (yyval.pval) = linku1((yyvsp[(1) - (3)].pval), nword((yyvsp[(3) - (3)].str), &(yylsp[(3) - (3)]))); }
     break;
 
   case 30:
 
 /* Line 1806 of yacc.c  */
-#line 258 "ael.y"
+#line 257 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (2)].pval);}
     break;
 
   case 31:
 
 /* Line 1806 of yacc.c  */
-#line 261 "ael.y"
+#line 260 "ael.y"
     {(yyval.pval)=0;}
     break;
 
   case 32:
 
 /* Line 1806 of yacc.c  */
-#line 262 "ael.y"
+#line 261 "ael.y"
     { (yyval.pval) = linku1((yyvsp[(1) - (2)].pval), (yyvsp[(2) - (2)].pval)); }
     break;
 
   case 33:
 
 /* Line 1806 of yacc.c  */
-#line 263 "ael.y"
+#line 262 "ael.y"
     { (yyval.pval)=(yyvsp[(2) - (2)].pval);}
     break;
 
   case 34:
 
 /* Line 1806 of yacc.c  */
-#line 266 "ael.y"
+#line 265 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (1)].pval);}
     break;
 
   case 35:
 
 /* Line 1806 of yacc.c  */
-#line 267 "ael.y"
+#line 266 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (1)].pval);}
     break;
 
   case 36:
 
 /* Line 1806 of yacc.c  */
-#line 268 "ael.y"
+#line 267 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (1)].pval);}
     break;
 
   case 37:
 
 /* Line 1806 of yacc.c  */
-#line 269 "ael.y"
+#line 268 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (1)].pval);}
     break;
 
   case 38:
 
 /* Line 1806 of yacc.c  */
-#line 270 "ael.y"
+#line 269 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (1)].pval);}
     break;
 
   case 39:
 
 /* Line 1806 of yacc.c  */
-#line 271 "ael.y"
+#line 270 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (1)].pval);}
     break;
 
   case 40:
 
 /* Line 1806 of yacc.c  */
-#line 272 "ael.y"
+#line 271 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (1)].pval);}
     break;
 
   case 41:
 
 /* Line 1806 of yacc.c  */
-#line 273 "ael.y"
+#line 272 "ael.y"
     {free((yyvsp[(1) - (2)].str)); (yyval.pval)=0;}
     break;
 
   case 42:
 
 /* Line 1806 of yacc.c  */
-#line 274 "ael.y"
+#line 273 "ael.y"
     {(yyval.pval)=0;/* allow older docs to be read */}
     break;
 
   case 43:
 
 /* Line 1806 of yacc.c  */
-#line 277 "ael.y"
+#line 276 "ael.y"
     {
 		(yyval.pval) = npval2(PV_IGNOREPAT, &(yylsp[(1) - (4)]), &(yylsp[(4) - (4)]));
 		(yyval.pval)->u1.str = (yyvsp[(3) - (4)].str);}
@@ -2635,7 +2635,7 @@ yyreduce:
   case 44:
 
 /* Line 1806 of yacc.c  */
-#line 282 "ael.y"
+#line 281 "ael.y"
     {
 		(yyval.pval) = npval2(PV_EXTENSION, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)]));
 		(yyval.pval)->u1.str = (yyvsp[(1) - (3)].str);
@@ -2645,7 +2645,7 @@ yyreduce:
   case 45:
 
 /* Line 1806 of yacc.c  */
-#line 286 "ael.y"
+#line 285 "ael.y"
     {
 		(yyval.pval) = npval2(PV_EXTENSION, &(yylsp[(1) - (5)]), &(yylsp[(3) - (5)]));
 		(yyval.pval)->u1.str = malloc(strlen((yyvsp[(1) - (5)].str))+strlen((yyvsp[(3) - (5)].str))+2);
@@ -2659,7 +2659,7 @@ yyreduce:
   case 46:
 
 /* Line 1806 of yacc.c  */
-#line 294 "ael.y"
+#line 293 "ael.y"
     {
 		(yyval.pval) = npval2(PV_EXTENSION, &(yylsp[(1) - (4)]), &(yylsp[(4) - (4)]));
 		(yyval.pval)->u1.str = (yyvsp[(2) - (4)].str);
@@ -2670,7 +2670,7 @@ yyreduce:
   case 47:
 
 /* Line 1806 of yacc.c  */
-#line 299 "ael.y"
+#line 298 "ael.y"
     {
 		(yyval.pval) = npval2(PV_EXTENSION, &(yylsp[(1) - (7)]), &(yylsp[(7) - (7)]));
 		(yyval.pval)->u1.str = (yyvsp[(5) - (7)].str);
@@ -2681,7 +2681,7 @@ yyreduce:
   case 48:
 
 /* Line 1806 of yacc.c  */
-#line 304 "ael.y"
+#line 303 "ael.y"
     {
 		(yyval.pval) = npval2(PV_EXTENSION, &(yylsp[(1) - (8)]), &(yylsp[(8) - (8)]));
 		(yyval.pval)->u1.str = (yyvsp[(6) - (8)].str);
@@ -2693,28 +2693,28 @@ yyreduce:
   case 49:
 
 /* Line 1806 of yacc.c  */
-#line 313 "ael.y"
+#line 312 "ael.y"
     { (yyval.pval) = NULL; }
     break;
 
   case 50:
 
 /* Line 1806 of yacc.c  */
-#line 314 "ael.y"
+#line 313 "ael.y"
     { (yyval.pval) = linku1((yyvsp[(1) - (2)].pval), (yyvsp[(2) - (2)].pval)); }
     break;
 
   case 51:
 
 /* Line 1806 of yacc.c  */
-#line 315 "ael.y"
+#line 314 "ael.y"
     {(yyval.pval)=(yyvsp[(2) - (2)].pval);}
     break;
 
   case 52:
 
 /* Line 1806 of yacc.c  */
-#line 321 "ael.y"
+#line 320 "ael.y"
     {
 		if (asprintf(&(yyval.str), "%s:%s:%s", (yyvsp[(1) - (5)].str), (yyvsp[(3) - (5)].str), (yyvsp[(5) - (5)].str)) < 0) {
 			ast_log(LOG_WARNING, "asprintf() failed\n");
@@ -2730,14 +2730,14 @@ yyreduce:
   case 53:
 
 /* Line 1806 of yacc.c  */
-#line 331 "ael.y"
+#line 330 "ael.y"
     { (yyval.str) = (yyvsp[(1) - (1)].str); }
     break;
 
   case 54:
 
 /* Line 1806 of yacc.c  */
-#line 335 "ael.y"
+#line 334 "ael.y"
     {
 		(yyval.pval) = nword((yyvsp[(1) - (7)].str), &(yylsp[(1) - (7)]));
 		(yyval.pval)->next = nword((yyvsp[(3) - (7)].str), &(yylsp[(3) - (7)]));
@@ -2748,21 +2748,21 @@ yyreduce:
   case 55:
 
 /* Line 1806 of yacc.c  */
-#line 343 "ael.y"
+#line 342 "ael.y"
     { reset_parencount(parseio->scanner); }
     break;
 
   case 56:
 
 /* Line 1806 of yacc.c  */
-#line 343 "ael.y"
+#line 342 "ael.y"
     { (yyval.str) = (yyvsp[(3) - (4)].str); }
     break;
 
   case 57:
 
 /* Line 1806 of yacc.c  */
-#line 347 "ael.y"
+#line 346 "ael.y"
     {
 		(yyval.pval)= npval2(PV_IF, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)]));
 		(yyval.pval)->u1.str = (yyvsp[(2) - (2)].str); }
@@ -2771,7 +2771,7 @@ yyreduce:
   case 58:
 
 /* Line 1806 of yacc.c  */
-#line 350 "ael.y"
+#line 349 "ael.y"
     {
 		(yyval.pval) = npval2(PV_RANDOM, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)]));
 		(yyval.pval)->u1.str=(yyvsp[(2) - (2)].str);}
@@ -2780,7 +2780,7 @@ yyreduce:
   case 59:
 
 /* Line 1806 of yacc.c  */
-#line 353 "ael.y"
+#line 352 "ael.y"
     {
 		(yyval.pval) = npval2(PV_IFTIME, &(yylsp[(1) - (4)]), &(yylsp[(4) - (4)]));
 		(yyval.pval)->u1.list = (yyvsp[(3) - (4)].pval);
@@ -2790,14 +2790,14 @@ yyreduce:
   case 60:
 
 /* Line 1806 of yacc.c  */
-#line 364 "ael.y"
+#line 363 "ael.y"
     { (yyval.str) = (yyvsp[(1) - (1)].str);}
     break;
 
   case 61:
 
 /* Line 1806 of yacc.c  */
-#line 365 "ael.y"
+#line 364 "ael.y"
     {
 		if (asprintf(&((yyval.str)), "%s%s", (yyvsp[(1) - (2)].str), (yyvsp[(2) - (2)].str)) < 0) {
 			ast_log(LOG_WARNING, "asprintf() failed\n");
@@ -2813,14 +2813,14 @@ yyreduce:
   case 62:
 
 /* Line 1806 of yacc.c  */
-#line 377 "ael.y"
+#line 376 "ael.y"
     { (yyval.str) = (yyvsp[(1) - (1)].str); }
     break;
 
   case 63:
 
 /* Line 1806 of yacc.c  */
-#line 378 "ael.y"
+#line 377 "ael.y"
     {
 		if (asprintf(&((yyval.str)), "%s %s", (yyvsp[(1) - (2)].str), (yyvsp[(2) - (2)].str)) < 0) {
 			ast_log(LOG_WARNING, "asprintf() failed\n");
@@ -2835,7 +2835,7 @@ yyreduce:
   case 64:
 
 /* Line 1806 of yacc.c  */
-#line 387 "ael.y"
+#line 386 "ael.y"
     {
 		if (asprintf(&((yyval.str)), "%s:%s", (yyvsp[(1) - (3)].str), (yyvsp[(3) - (3)].str)) < 0) {
 			ast_log(LOG_WARNING, "asprintf() failed\n");
@@ -2850,7 +2850,7 @@ yyreduce:
   case 65:
 
 /* Line 1806 of yacc.c  */
-#line 396 "ael.y"
+#line 395 "ael.y"
     {  /* there are often '&' in hints */
 		if (asprintf(&((yyval.str)), "%s&%s", (yyvsp[(1) - (3)].str), (yyvsp[(3) - (3)].str)) < 0) {
 			ast_log(LOG_WARNING, "asprintf() failed\n");
@@ -2865,7 +2865,7 @@ yyreduce:
   case 66:
 
 /* Line 1806 of yacc.c  */
-#line 405 "ael.y"
+#line 404 "ael.y"
     {
 		if (asprintf(&((yyval.str)), "%s@%s", (yyvsp[(1) - (3)].str), (yyvsp[(3) - (3)].str)) < 0) {
 			ast_log(LOG_WARNING, "asprintf() failed\n");
@@ -2880,14 +2880,14 @@ yyreduce:
   case 67:
 
 /* Line 1806 of yacc.c  */
-#line 416 "ael.y"
+#line 415 "ael.y"
     { (yyval.str) = (yyvsp[(1) - (1)].str);}
     break;
 
   case 68:
 
 /* Line 1806 of yacc.c  */
-#line 417 "ael.y"
+#line 416 "ael.y"
     {
 		if (asprintf(&((yyval.str)), "%s%s", (yyvsp[(1) - (2)].str), (yyvsp[(2) - (2)].str)) < 0) {
 			ast_log(LOG_WARNING, "asprintf() failed\n");
@@ -2903,7 +2903,7 @@ yyreduce:
   case 69:
 
 /* Line 1806 of yacc.c  */
-#line 427 "ael.y"
+#line 426 "ael.y"
     {
 		if (asprintf(&((yyval.str)), "%s%s%s", (yyvsp[(1) - (3)].str), (yyvsp[(2) - (3)].str), (yyvsp[(3) - (3)].str)) < 0) {
 			ast_log(LOG_WARNING, "asprintf() failed\n");
@@ -2920,14 +2920,14 @@ yyreduce:
   case 70:
 
 /* Line 1806 of yacc.c  */
-#line 440 "ael.y"
+#line 439 "ael.y"
     { (yyval.str) = (yyvsp[(1) - (1)].str);}
     break;
 
   case 71:
 
 /* Line 1806 of yacc.c  */
-#line 441 "ael.y"
+#line 440 "ael.y"
     {
 		if (asprintf(&((yyval.str)), "%s%s", (yyvsp[(1) - (2)].str), (yyvsp[(2) - (2)].str)) < 0) {
 			ast_log(LOG_WARNING, "asprintf() failed\n");
@@ -2942,7 +2942,7 @@ yyreduce:
   case 72:
 
 /* Line 1806 of yacc.c  */
-#line 450 "ael.y"
+#line 449 "ael.y"
     {
 		if (asprintf(&((yyval.str)), "%s:%s", (yyvsp[(1) - (3)].str), (yyvsp[(3) - (3)].str)) < 0) {
 			ast_log(LOG_WARNING, "asprintf() failed\n");
@@ -2957,7 +2957,7 @@ yyreduce:
   case 73:
 
 /* Line 1806 of yacc.c  */
-#line 461 "ael.y"
+#line 460 "ael.y"
     {
 		(yyval.pval) = npval2(PV_SWITCH, &(yylsp[(1) - (5)]), &(yylsp[(5) - (5)]));
 		(yyval.pval)->u1.str = (yyvsp[(2) - (5)].str);
@@ -2967,7 +2967,7 @@ yyreduce:
   case 74:
 
 /* Line 1806 of yacc.c  */
-#line 470 "ael.y"
+#line 469 "ael.y"
     {
 		(yyval.pval) = npval2(PV_STATEMENTBLOCK, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)]));
 		(yyval.pval)->u1.list = (yyvsp[(2) - (3)].pval); set_dads((yyval.pval),(yyvsp[(2) - (3)].pval));}
@@ -2976,21 +2976,21 @@ yyreduce:
   case 75:
 
 /* Line 1806 of yacc.c  */
-#line 473 "ael.y"
+#line 472 "ael.y"
     { (yyval.pval) = (yyvsp[(1) - (1)].pval); }
     break;
 
   case 76:
 
 /* Line 1806 of yacc.c  */
-#line 474 "ael.y"
+#line 473 "ael.y"
     { (yyval.pval) = (yyvsp[(1) - (1)].pval); }
     break;
 
   case 77:
 
 /* Line 1806 of yacc.c  */
-#line 475 "ael.y"
+#line 474 "ael.y"
     {
 		(yyval.pval) = npval2(PV_GOTO, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)]));
 		(yyval.pval)->u1.list = (yyvsp[(2) - (3)].pval);}
@@ -2999,7 +2999,7 @@ yyreduce:
   case 78:
 
 /* Line 1806 of yacc.c  */
-#line 478 "ael.y"
+#line 477 "ael.y"
     {
 		(yyval.pval) = npval2(PV_GOTO, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)]));
 		(yyval.pval)->u1.list = (yyvsp[(2) - (3)].pval);}
@@ -3008,7 +3008,7 @@ yyreduce:
   case 79:
 
 /* Line 1806 of yacc.c  */
-#line 481 "ael.y"
+#line 480 "ael.y"
     {
 		(yyval.pval) = npval2(PV_LABEL, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)]));
 		(yyval.pval)->u1.str = (yyvsp[(1) - (2)].str); }
@@ -3017,28 +3017,28 @@ yyreduce:
   case 80:
 
 /* Line 1806 of yacc.c  */
-#line 484 "ael.y"
+#line 483 "ael.y"
     {reset_semicount(parseio->scanner);}
     break;
 
   case 81:
 
 /* Line 1806 of yacc.c  */
-#line 485 "ael.y"
+#line 484 "ael.y"
     {reset_semicount(parseio->scanner);}
     break;
 
   case 82:
 
 /* Line 1806 of yacc.c  */
-#line 486 "ael.y"
+#line 485 "ael.y"
     {reset_parencount(parseio->scanner);}
     break;
 
   case 83:
 
 /* Line 1806 of yacc.c  */
-#line 486 "ael.y"
+#line 485 "ael.y"
     { /* XXX word_list maybe ? */
 		(yyval.pval) = npval2(PV_FOR, &(yylsp[(1) - (12)]), &(yylsp[(12) - (12)]));
 		(yyval.pval)->u1.for_init = (yyvsp[(4) - (12)].str);
@@ -3050,7 +3050,7 @@ yyreduce:
   case 84:
 
 /* Line 1806 of yacc.c  */
-#line 492 "ael.y"
+#line 491 "ael.y"
     {
 		(yyval.pval) = npval2(PV_WHILE, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)]));
 		(yyval.pval)->u1.str = (yyvsp[(2) - (3)].str);
@@ -3060,28 +3060,28 @@ yyreduce:
   case 85:
 
 /* Line 1806 of yacc.c  */
-#line 496 "ael.y"
+#line 495 "ael.y"
     { (yyval.pval) = (yyvsp[(1) - (1)].pval); }
     break;
 
   case 86:
 
 /* Line 1806 of yacc.c  */
-#line 497 "ael.y"
+#line 496 "ael.y"
     { (yyval.pval) = update_last((yyvsp[(2) - (3)].pval), &(yylsp[(2) - (3)])); }
     break;
 
   case 87:
 
 /* Line 1806 of yacc.c  */
-#line 498 "ael.y"
+#line 497 "ael.y"
     { (yyval.pval) = update_last((yyvsp[(1) - (2)].pval), &(yylsp[(2) - (2)])); }
     break;
 
   case 88:
 
 /* Line 1806 of yacc.c  */
-#line 499 "ael.y"
+#line 498 "ael.y"
     {
 		(yyval.pval)= npval2(PV_APPLICATION_CALL, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)]));
 		(yyval.pval)->u1.str = (yyvsp[(1) - (2)].str);}
@@ -3090,14 +3090,14 @@ yyreduce:
   case 89:
 
 /* Line 1806 of yacc.c  */
-#line 502 "ael.y"
+#line 501 "ael.y"
     {reset_semicount(parseio->scanner);}
     break;
 
   case 90:
 
 /* Line 1806 of yacc.c  */
-#line 502 "ael.y"
+#line 501 "ael.y"
     {
 		char *bufx;
 		int tot=0;
@@ -3136,28 +3136,28 @@ yyreduce:
   case 91:
 
 /* Line 1806 of yacc.c  */
-#line 535 "ael.y"
+#line 534 "ael.y"
     { (yyval.pval) = npval2(PV_BREAK, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)])); }
     break;
 
   case 92:
 
 /* Line 1806 of yacc.c  */
-#line 536 "ael.y"
+#line 535 "ael.y"
     { (yyval.pval) = npval2(PV_RETURN, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)])); }
     break;
 
   case 93:
 
 /* Line 1806 of yacc.c  */
-#line 537 "ael.y"
+#line 536 "ael.y"
     { (yyval.pval) = npval2(PV_CONTINUE, &(yylsp[(1) - (2)]), &(yylsp[(2) - (2)])); }
     break;
 
   case 94:
 
 /* Line 1806 of yacc.c  */
-#line 538 "ael.y"
+#line 537 "ael.y"
     {
 		(yyval.pval) = update_last((yyvsp[(1) - (3)].pval), &(yylsp[(2) - (3)]));
 		(yyval.pval)->u2.statements = (yyvsp[(2) - (3)].pval); set_dads((yyval.pval),(yyvsp[(2) - (3)].pval));
@@ -3167,35 +3167,35 @@ yyreduce:
   case 95:
 
 /* Line 1806 of yacc.c  */
-#line 542 "ael.y"
+#line 541 "ael.y"
     { (yyval.pval)=0; }
     break;
 
   case 96:
 
 /* Line 1806 of yacc.c  */
-#line 545 "ael.y"
+#line 544 "ael.y"
     { (yyval.pval) = (yyvsp[(2) - (2)].pval); }
     break;
 
   case 97:
 
 /* Line 1806 of yacc.c  */
-#line 546 "ael.y"
+#line 545 "ael.y"
     { (yyval.pval) = NULL ; }
     break;
 
   case 98:
 
 /* Line 1806 of yacc.c  */
-#line 549 "ael.y"
+#line 548 "ael.y"
     { (yyval.pval) = nword((yyvsp[(1) - (1)].str), &(yylsp[(1) - (1)])); }
     break;
 
   case 99:
 
 /* Line 1806 of yacc.c  */
-#line 550 "ael.y"
+#line 549 "ael.y"
     {
 		(yyval.pval) = nword((yyvsp[(1) - (3)].str), &(yylsp[(1) - (3)]));
 		(yyval.pval)->next = nword((yyvsp[(3) - (3)].str), &(yylsp[(3) - (3)])); }
@@ -3204,7 +3204,7 @@ yyreduce:
   case 100:
 
 /* Line 1806 of yacc.c  */
-#line 553 "ael.y"
+#line 552 "ael.y"
     {
 		(yyval.pval) = nword((yyvsp[(1) - (3)].str), &(yylsp[(1) - (3)]));
 		(yyval.pval)->next = nword((yyvsp[(3) - (3)].str), &(yylsp[(3) - (3)])); }
@@ -3213,7 +3213,7 @@ yyreduce:
   case 101:
 
 /* Line 1806 of yacc.c  */
-#line 556 "ael.y"
+#line 555 "ael.y"
     {
 		(yyval.pval) = nword((yyvsp[(1) - (5)].str), &(yylsp[(1) - (5)]));
 		(yyval.pval)->next = nword((yyvsp[(3) - (5)].str), &(yylsp[(3) - (5)]));
@@ -3223,7 +3223,7 @@ yyreduce:
   case 102:
 
 /* Line 1806 of yacc.c  */
-#line 560 "ael.y"
+#line 559 "ael.y"
     {
 		(yyval.pval) = nword((yyvsp[(1) - (5)].str), &(yylsp[(1) - (5)]));
 		(yyval.pval)->next = nword((yyvsp[(3) - (5)].str), &(yylsp[(3) - (5)]));
@@ -3233,7 +3233,7 @@ yyreduce:
   case 103:
 
 /* Line 1806 of yacc.c  */
-#line 564 "ael.y"
+#line 563 "ael.y"
     {
 		(yyval.pval) = nword(strdup("default"), &(yylsp[(1) - (5)]));
 		(yyval.pval)->next = nword((yyvsp[(3) - (5)].str), &(yylsp[(3) - (5)]));
@@ -3243,7 +3243,7 @@ yyreduce:
   case 104:
 
 /* Line 1806 of yacc.c  */
-#line 568 "ael.y"
+#line 567 "ael.y"
     {
 		(yyval.pval) = nword(strdup("default"), &(yylsp[(1) - (5)]));
 		(yyval.pval)->next = nword((yyvsp[(3) - (5)].str), &(yylsp[(3) - (5)]));
@@ -3253,21 +3253,21 @@ yyreduce:
   case 105:
 
 /* Line 1806 of yacc.c  */
-#line 574 "ael.y"
+#line 573 "ael.y"
     { (yyval.str) = strdup("1"); }
     break;
 
   case 106:
 
 /* Line 1806 of yacc.c  */
-#line 575 "ael.y"
+#line 574 "ael.y"
     { (yyval.str) = (yyvsp[(2) - (2)].str); }
     break;
 
   case 107:
 
 /* Line 1806 of yacc.c  */
-#line 579 "ael.y"
+#line 578 "ael.y"
     {			/* ext[, pri] default 1 */
 		(yyval.pval) = nword((yyvsp[(1) - (2)].str), &(yylsp[(1) - (2)]));
 		(yyval.pval)->next = nword((yyvsp[(2) - (2)].str), &(yylsp[(2) - (2)])); }
@@ -3276,7 +3276,7 @@ yyreduce:
   case 108:
 
 /* Line 1806 of yacc.c  */
-#line 582 "ael.y"
+#line 581 "ael.y"
     {	/* context, ext, pri */
 		(yyval.pval) = nword((yyvsp[(4) - (4)].str), &(yylsp[(4) - (4)]));
 		(yyval.pval)->next = nword((yyvsp[(1) - (4)].str), &(yylsp[(1) - (4)]));
@@ -3286,14 +3286,14 @@ yyreduce:
   case 109:
 
 /* Line 1806 of yacc.c  */
-#line 588 "ael.y"
+#line 587 "ael.y"
     {reset_argcount(parseio->scanner);}
     break;
 
   case 110:
 
 /* Line 1806 of yacc.c  */
-#line 588 "ael.y"
+#line 587 "ael.y"
     {
 		/* XXX original code had @2 but i think we need @5 */
 		(yyval.pval) = npval2(PV_MACRO_CALL, &(yylsp[(1) - (5)]), &(yylsp[(5) - (5)]));
@@ -3304,7 +3304,7 @@ yyreduce:
   case 111:
 
 /* Line 1806 of yacc.c  */
-#line 593 "ael.y"
+#line 592 "ael.y"
     {
 		(yyval.pval)= npval2(PV_MACRO_CALL, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)]));
 		(yyval.pval)->u1.str = (yyvsp[(1) - (3)].str); }
@@ -3313,14 +3313,14 @@ yyreduce:
   case 112:
 
 /* Line 1806 of yacc.c  */
-#line 601 "ael.y"
+#line 600 "ael.y"
     {reset_argcount(parseio->scanner);}
     break;
 
   case 113:
 
 /* Line 1806 of yacc.c  */
-#line 601 "ael.y"
+#line 600 "ael.y"
     {
 		if (strcasecmp((yyvsp[(1) - (3)].str),"goto") == 0) {
 			(yyval.pval) = npval2(PV_GOTO, &(yylsp[(1) - (3)]), &(yylsp[(2) - (3)]));
@@ -3335,7 +3335,7 @@ yyreduce:
   case 114:
 
 /* Line 1806 of yacc.c  */
-#line 612 "ael.y"
+#line 611 "ael.y"
     {
 		(yyval.pval) = update_last((yyvsp[(1) - (3)].pval), &(yylsp[(3) - (3)]));
  		if( (yyval.pval)->type == PV_GOTO )
@@ -3348,35 +3348,35 @@ yyreduce:
   case 115:
 
 /* Line 1806 of yacc.c  */
-#line 619 "ael.y"
+#line 618 "ael.y"
     { (yyval.pval) = update_last((yyvsp[(1) - (2)].pval), &(yylsp[(2) - (2)])); }
     break;
 
   case 116:
 
 /* Line 1806 of yacc.c  */
-#line 622 "ael.y"
+#line 621 "ael.y"
     { (yyval.str) = (yyvsp[(1) - (1)].str) ;}
     break;
 
   case 117:
 
 /* Line 1806 of yacc.c  */
-#line 623 "ael.y"
+#line 622 "ael.y"
     { (yyval.str) = strdup(""); }
     break;
 
   case 118:
 
 /* Line 1806 of yacc.c  */
-#line 626 "ael.y"
+#line 625 "ael.y"
     { (yyval.pval) = nword((yyvsp[(1) - (1)].str), &(yylsp[(1) - (1)])); }
     break;
 
   case 119:
 
 /* Line 1806 of yacc.c  */
-#line 627 "ael.y"
+#line 626 "ael.y"
     {
 		(yyval.pval)= npval(PV_WORD,0/*@1.first_line*/,0/*@1.last_line*/,0/* @1.first_column*/, 0/*@1.last_column*/);
 		(yyval.pval)->u1.str = strdup(""); }
@@ -3385,28 +3385,28 @@ yyreduce:
   case 120:
 
 /* Line 1806 of yacc.c  */
-#line 630 "ael.y"
+#line 629 "ael.y"
     { (yyval.pval) = linku1((yyvsp[(1) - (3)].pval), nword((yyvsp[(3) - (3)].str), &(yylsp[(3) - (3)]))); }
     break;
 
   case 121:
 
 /* Line 1806 of yacc.c  */
-#line 633 "ael.y"
+#line 632 "ael.y"
     { (yyval.pval) = NULL; }
     break;
 
   case 122:
 
 /* Line 1806 of yacc.c  */
-#line 634 "ael.y"
+#line 633 "ael.y"
     { (yyval.pval) = linku1((yyvsp[(1) - (2)].pval), (yyvsp[(2) - (2)].pval)); }
     break;
 
   case 123:
 
 /* Line 1806 of yacc.c  */
-#line 637 "ael.y"
+#line 636 "ael.y"
     {
 		(yyval.pval) = npval2(PV_CASE, &(yylsp[(1) - (4)]), &(yylsp[(3) - (4)])); /* XXX 3 or 4 ? */
 		(yyval.pval)->u1.str = (yyvsp[(2) - (4)].str);
@@ -3416,7 +3416,7 @@ yyreduce:
   case 124:
 
 /* Line 1806 of yacc.c  */
-#line 641 "ael.y"
+#line 640 "ael.y"
     {
 		(yyval.pval) = npval2(PV_DEFAULT, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)]));
 		(yyval.pval)->u1.str = NULL;
@@ -3426,7 +3426,7 @@ yyreduce:
   case 125:
 
 /* Line 1806 of yacc.c  */
-#line 645 "ael.y"
+#line 644 "ael.y"
     {
 		(yyval.pval) = npval2(PV_PATTERN, &(yylsp[(1) - (4)]), &(yylsp[(4) - (4)])); /* XXX@3 or @4 ? */
 		(yyval.pval)->u1.str = (yyvsp[(2) - (4)].str);
@@ -3436,35 +3436,35 @@ yyreduce:
   case 126:
 
 /* Line 1806 of yacc.c  */
-#line 651 "ael.y"
+#line 650 "ael.y"
     { (yyval.pval) = NULL; }
     break;
 
   case 127:
 
 /* Line 1806 of yacc.c  */
-#line 652 "ael.y"
+#line 651 "ael.y"
     { (yyval.pval) = linku1((yyvsp[(1) - (2)].pval), (yyvsp[(2) - (2)].pval)); }
     break;
 
   case 128:
 
 /* Line 1806 of yacc.c  */
-#line 655 "ael.y"
+#line 654 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (1)].pval);}
     break;
 
   case 129:
 
 /* Line 1806 of yacc.c  */
-#line 656 "ael.y"
+#line 655 "ael.y"
     { (yyval.pval)=(yyvsp[(1) - (1)].pval);}
     break;
 
   case 130:
 
 /* Line 1806 of yacc.c  */
-#line 657 "ael.y"
+#line 656 "ael.y"
     {
 		(yyval.pval) = npval2(PV_CATCH, &(yylsp[(1) - (5)]), &(yylsp[(5) - (5)]));
 		(yyval.pval)->u1.str = (yyvsp[(2) - (5)].str);
@@ -3474,7 +3474,7 @@ yyreduce:
   case 131:
 
 /* Line 1806 of yacc.c  */
-#line 663 "ael.y"
+#line 662 "ael.y"
     {
 		(yyval.pval) = npval2(PV_SWITCHES, &(yylsp[(1) - (4)]), &(yylsp[(2) - (4)]));
 		(yyval.pval)->u1.list = (yyvsp[(3) - (4)].pval); set_dads((yyval.pval),(yyvsp[(3) - (4)].pval));}
@@ -3483,7 +3483,7 @@ yyreduce:
   case 132:
 
 /* Line 1806 of yacc.c  */
-#line 668 "ael.y"
+#line 667 "ael.y"
     {
 		(yyval.pval) = npval2(PV_ESWITCHES, &(yylsp[(1) - (4)]), &(yylsp[(2) - (4)]));
 		(yyval.pval)->u1.list = (yyvsp[(3) - (4)].pval); set_dads((yyval.pval),(yyvsp[(3) - (4)].pval));}
@@ -3492,21 +3492,21 @@ yyreduce:
   case 133:
 
 /* Line 1806 of yacc.c  */
-#line 673 "ael.y"
+#line 672 "ael.y"
     { (yyval.pval) = NULL; }
     break;
 
   case 134:
 
 /* Line 1806 of yacc.c  */
-#line 674 "ael.y"
+#line 673 "ael.y"
     { (yyval.pval) = linku1((yyvsp[(1) - (3)].pval),nword((yyvsp[(2) - (3)].str), &(yylsp[(2) - (3)]))); }
     break;
 
   case 135:
 
 /* Line 1806 of yacc.c  */
-#line 675 "ael.y"
+#line 674 "ael.y"
     {
 	  char *x;
 	  if (asprintf(&x,"%s@%s", (yyvsp[(2) - (5)].str), (yyvsp[(4) - (5)].str)) < 0) {
@@ -3523,21 +3523,21 @@ yyreduce:
   case 136:
 
 /* Line 1806 of yacc.c  */
-#line 686 "ael.y"
+#line 685 "ael.y"
     {(yyval.pval)=(yyvsp[(2) - (2)].pval);}
     break;
 
   case 137:
 
 /* Line 1806 of yacc.c  */
-#line 689 "ael.y"
+#line 688 "ael.y"
     { (yyval.pval) = nword((yyvsp[(1) - (1)].str), &(yylsp[(1) - (1)])); }
     break;
 
   case 138:
 
 /* Line 1806 of yacc.c  */
-#line 690 "ael.y"
+#line 689 "ael.y"
     {
 		(yyval.pval) = nword((yyvsp[(1) - (3)].str), &(yylsp[(1) - (3)]));
 		(yyval.pval)->u2.arglist = (yyvsp[(3) - (3)].pval);
@@ -3547,28 +3547,28 @@ yyreduce:
   case 139:
 
 /* Line 1806 of yacc.c  */
-#line 697 "ael.y"
+#line 696 "ael.y"
     { (yyval.pval) = (yyvsp[(1) - (2)].pval); }
     break;
 
   case 140:
 
 /* Line 1806 of yacc.c  */
-#line 698 "ael.y"
+#line 697 "ael.y"
     { (yyval.pval) = linku1((yyvsp[(1) - (3)].pval), (yyvsp[(2) - (3)].pval)); }
     break;
 
   case 141:
 
 /* Line 1806 of yacc.c  */
-#line 699 "ael.y"
+#line 698 "ael.y"
     {(yyval.pval)=(yyvsp[(1) - (2)].pval);}
     break;
 
   case 142:
 
 /* Line 1806 of yacc.c  */
-#line 702 "ael.y"
+#line 701 "ael.y"
     {
 		(yyval.pval) = npval2(PV_INCLUDES, &(yylsp[(1) - (4)]), &(yylsp[(4) - (4)]));
 		(yyval.pval)->u1.list = (yyvsp[(3) - (4)].pval);set_dads((yyval.pval),(yyvsp[(3) - (4)].pval));}
@@ -3577,7 +3577,7 @@ yyreduce:
   case 143:
 
 /* Line 1806 of yacc.c  */
-#line 705 "ael.y"
+#line 704 "ael.y"
     {
 		(yyval.pval) = npval2(PV_INCLUDES, &(yylsp[(1) - (3)]), &(yylsp[(3) - (3)]));}
     break;
@@ -3585,7 +3585,7 @@ yyreduce:
 
 
 /* Line 1806 of yacc.c  */
-#line 3590 "ael.tab.c"
+#line 3589 "ael.tab.c"
       default: break;
     }
   /* User semantic actions sometimes alter yychar, and that requires
@@ -3823,7 +3823,7 @@ yyreturn:
 
 
 /* Line 2067 of yacc.c  */
-#line 710 "ael.y"
+#line 709 "ael.y"
 
 
 static char *token_equivs1[] =
@@ -4005,3 +4005,4 @@ static void set_dads(struct pval *dad, struct pval *child_list)
 	for(t=child_list;t;t=t->next)  /* simple stuff */
 		t->dad = dad;
 }
+
diff --git a/res/ael/ael.tab.h b/res/ael/ael.tab.h
index 1647649a8e58bdf2eb8f499404afc0fce954ddd4..5ea6c0f166717e5321da579000f3938453a02a19 100644
--- a/res/ael/ael.tab.h
+++ b/res/ael/ael.tab.h
@@ -1,19 +1,19 @@
 /* A Bison parser, made by GNU Bison 2.5.  */
 
 /* Bison interface for Yacc-like parsers in C
-
+   
       Copyright (C) 1984, 1989-1990, 2000-2011 Free Software Foundation, Inc.
-
+   
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
-
+   
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
-
+   
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
@@ -26,7 +26,7 @@
    special exception, which will cause the skeleton and the resulting
    Bison output files to be licensed under the GNU General Public
    License without this special exception.
-
+   
    This special exception was added by the Free Software Foundation in
    version 2.2 of Bison.  */
 
@@ -88,7 +88,7 @@ typedef union YYSTYPE
 {
 
 /* Line 2068 of yacc.c  */
-#line 59 "ael.y"
+#line 58 "ael.y"
 
 	int	intval;		/* integer value, typically flags */
 	char	*str;		/* strings */
@@ -118,3 +118,6 @@ typedef struct YYLTYPE
 # define YYLTYPE_IS_DECLARED 1
 # define YYLTYPE_IS_TRIVIAL 1
 #endif
+
+
+
diff --git a/res/ael/ael_lex.c b/res/ael/ael_lex.c
index 04821fe2b8172b0418a7713f142cfa97a76e65ce..b54a27205b8b5fba7bb550b6b4315593670890a8 100644
--- a/res/ael/ael_lex.c
+++ b/res/ael/ael_lex.c
@@ -37,7 +37,7 @@
 #if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
 
 /* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
- * if you want the limit (max/min) macros for int types.
+ * if you want the limit (max/min) macros for int types. 
  */
 #ifndef __STDC_LIMIT_MACROS
 #define __STDC_LIMIT_MACROS 1
@@ -54,9 +54,10 @@ typedef uint32_t flex_uint32_t;
 typedef signed char flex_int8_t;
 typedef short int flex_int16_t;
 typedef int flex_int32_t;
-typedef unsigned char flex_uint8_t;
+typedef unsigned char flex_uint8_t; 
 typedef unsigned short int flex_uint16_t;
 typedef unsigned int flex_uint32_t;
+#endif /* ! C99 */
 
 /* Limits of integral types. */
 #ifndef INT8_MIN
@@ -87,8 +88,6 @@ typedef unsigned int flex_uint32_t;
 #define UINT32_MAX             (4294967295U)
 #endif
 
-#endif /* ! C99 */
-
 #endif /* ! FLEXINT_H */
 
 #ifdef __cplusplus
@@ -162,15 +161,7 @@ typedef void* yyscan_t;
 
 /* Size of default input buffer. */
 #ifndef YY_BUF_SIZE
-#ifdef __ia64__
-/* On IA-64, the buffer size is 16k, not 8k.
- * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case.
- * Ditto for the __ia64__ case accordingly.
- */
-#define YY_BUF_SIZE 32768
-#else
 #define YY_BUF_SIZE 16384
-#endif /* __ia64__ */
 #endif
 
 /* The state buf must be large enough to hold one state per character in the main buffer.
@@ -187,7 +178,7 @@ typedef struct yy_buffer_state *YY_BUFFER_STATE;
 #define EOB_ACT_LAST_MATCH 2
 
     #define YY_LESS_LINENO(n)
-
+    
 /* Return all but the first "n" matched characters back to the input stream. */
 #define yyless(n) \
 	do \
@@ -249,7 +240,7 @@ struct yy_buffer_state
 
     int yy_bs_lineno; /**< The line count. */
     int yy_bs_column; /**< The column count. */
-
+    
 	/* Whether to try to fill the input buffer when we reach the
 	 * end of it.
 	 */
@@ -828,8 +819,6 @@ static yyconst flex_int16_t yy_chk[1073] =
  * bison-locations is probably not needed.
  */
 #line 71 "ael.flex"
-#include "asterisk.h"
-
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
@@ -967,7 +956,7 @@ static void pbcwhere(const char *text, int *line, int *col )
 #define	STORE_POS
 #define	STORE_LOC
 #endif
-#line 963 "ael_lex.c"
+#line 957 "ael_lex.c"
 
 #define INITIAL 0
 #define paren 1
@@ -1033,9 +1022,9 @@ static int yy_init_globals (yyscan_t yyscanner );
     /* This must go here because YYSTYPE and YYLTYPE are included
      * from bison output in section 1.*/
     #    define yylval yyg->yylval_r
-
+    
     #    define yylloc yyg->yylloc_r
-
+    
 int ael_yylex_init (yyscan_t* scanner);
 
 int ael_yylex_init_extra (YY_EXTRA_TYPE user_defined,yyscan_t* scanner);
@@ -1074,9 +1063,9 @@ YYSTYPE * ael_yyget_lval (yyscan_t yyscanner );
 void ael_yyset_lval (YYSTYPE * yylval_param ,yyscan_t yyscanner );
 
        YYLTYPE *ael_yyget_lloc (yyscan_t yyscanner );
-
+    
         void ael_yyset_lloc (YYLTYPE * yylloc_param ,yyscan_t yyscanner );
-
+    
 /* Macros after this point can all be overridden by user definitions in
  * section 1.
  */
@@ -1090,7 +1079,7 @@ extern int ael_yywrap (yyscan_t yyscanner );
 #endif
 
     static void yyunput (int c,char *buf_ptr  ,yyscan_t yyscanner);
-
+    
 #ifndef yytext_ptr
 static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner);
 #endif
@@ -1111,12 +1100,7 @@ static int input (yyscan_t yyscanner );
 
 /* Amount of stuff to slurp up with each read. */
 #ifndef YY_READ_BUF_SIZE
-#ifdef __ia64__
-/* On IA-64, the buffer size is 16k, not 8k */
-#define YY_READ_BUF_SIZE 16384
-#else
 #define YY_READ_BUF_SIZE 8192
-#endif /* __ia64__ */
 #endif
 
 /* Copy whatever the last rule matched to the standard output. */
@@ -1124,7 +1108,7 @@ static int input (yyscan_t yyscanner );
 /* This used to be an fputs(), but since the string might contain NUL's,
  * we now use fwrite().
  */
-#define ECHO do { if (fwrite( yytext, yyleng, 1, yyout )) {} } while (0)
+#define ECHO fwrite( yytext, yyleng, 1, yyout )
 #endif
 
 /* Gets input and stuffs it into "buf".  number of characters read, or YY_NULL,
@@ -1135,7 +1119,7 @@ static int input (yyscan_t yyscanner );
 	if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
 		{ \
 		int c = '*'; \
-		size_t n; \
+		int n; \
 		for ( n = 0; n < max_size && \
 			     (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
 			buf[n] = (char) c; \
@@ -1220,10 +1204,10 @@ YY_DECL
 	register int yy_act;
     struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
 
-#line 217 "ael.flex"
+#line 220 "ael.flex"
 
 
-#line 1219 "ael_lex.c"
+#line 1208 "ael_lex.c"
 
     yylval = yylval_param;
 
@@ -1314,260 +1298,260 @@ do_action:	/* This label is used only to access EOF actions. */
 
 case 1:
 YY_RULE_SETUP
-#line 219 "ael.flex"
+#line 222 "ael.flex"
 { STORE_POS; return LC;}
 	YY_BREAK
 case 2:
 YY_RULE_SETUP
-#line 220 "ael.flex"
+#line 223 "ael.flex"
 { STORE_POS; return RC;}
 	YY_BREAK
 case 3:
 YY_RULE_SETUP
-#line 221 "ael.flex"
+#line 224 "ael.flex"
 { STORE_POS; return LP;}
 	YY_BREAK
 case 4:
 YY_RULE_SETUP
-#line 222 "ael.flex"
+#line 225 "ael.flex"
 { STORE_POS; return RP;}
 	YY_BREAK
 case 5:
 YY_RULE_SETUP
-#line 223 "ael.flex"
+#line 226 "ael.flex"
 { STORE_POS; return SEMI;}
 	YY_BREAK
 case 6:
 YY_RULE_SETUP
-#line 224 "ael.flex"
+#line 227 "ael.flex"
 { STORE_POS; return EQ;}
 	YY_BREAK
 case 7:
 YY_RULE_SETUP
-#line 225 "ael.flex"
+#line 228 "ael.flex"
 { STORE_POS; return COMMA;}
 	YY_BREAK
 case 8:
 YY_RULE_SETUP
-#line 226 "ael.flex"
+#line 229 "ael.flex"
 { STORE_POS; return COLON;}
 	YY_BREAK
 case 9:
 YY_RULE_SETUP
-#line 227 "ael.flex"
+#line 230 "ael.flex"
 { STORE_POS; return AMPER;}
 	YY_BREAK
 case 10:
 YY_RULE_SETUP
-#line 228 "ael.flex"
+#line 231 "ael.flex"
 { STORE_POS; return BAR;}
 	YY_BREAK
 case 11:
 YY_RULE_SETUP
-#line 229 "ael.flex"
+#line 232 "ael.flex"
 { STORE_POS; return EXTENMARK;}
 	YY_BREAK
 case 12:
 YY_RULE_SETUP
-#line 230 "ael.flex"
+#line 233 "ael.flex"
 { STORE_POS; return AT;}
 	YY_BREAK
 case 13:
 YY_RULE_SETUP
-#line 231 "ael.flex"
+#line 234 "ael.flex"
 {/*comment*/}
 	YY_BREAK
 case 14:
 YY_RULE_SETUP
-#line 232 "ael.flex"
+#line 235 "ael.flex"
 { STORE_POS; return KW_CONTEXT;}
 	YY_BREAK
 case 15:
 YY_RULE_SETUP
-#line 233 "ael.flex"
+#line 236 "ael.flex"
 { STORE_POS; return KW_ABSTRACT;}
 	YY_BREAK
 case 16:
 YY_RULE_SETUP
-#line 234 "ael.flex"
+#line 237 "ael.flex"
 { STORE_POS; return KW_EXTEND;}
 	YY_BREAK
 case 17:
 YY_RULE_SETUP
-#line 235 "ael.flex"
+#line 238 "ael.flex"
 { STORE_POS; return KW_MACRO;};
 	YY_BREAK
 case 18:
 YY_RULE_SETUP
-#line 236 "ael.flex"
+#line 239 "ael.flex"
 { STORE_POS; return KW_GLOBALS;}
 	YY_BREAK
 case 19:
 YY_RULE_SETUP
-#line 237 "ael.flex"
+#line 240 "ael.flex"
 { STORE_POS; return KW_LOCAL;}
 	YY_BREAK
 case 20:
 YY_RULE_SETUP
-#line 238 "ael.flex"
+#line 241 "ael.flex"
 { STORE_POS; return KW_IGNOREPAT;}
 	YY_BREAK
 case 21:
 YY_RULE_SETUP
-#line 239 "ael.flex"
+#line 242 "ael.flex"
 { STORE_POS; return KW_SWITCH;}
 	YY_BREAK
 case 22:
 YY_RULE_SETUP
-#line 240 "ael.flex"
+#line 243 "ael.flex"
 { STORE_POS; return KW_IF;}
 	YY_BREAK
 case 23:
 YY_RULE_SETUP
-#line 241 "ael.flex"
+#line 244 "ael.flex"
 { STORE_POS; return KW_IFTIME;}
 	YY_BREAK
 case 24:
 YY_RULE_SETUP
-#line 242 "ael.flex"
+#line 245 "ael.flex"
 { STORE_POS; return KW_RANDOM;}
 	YY_BREAK
 case 25:
 YY_RULE_SETUP
-#line 243 "ael.flex"
+#line 246 "ael.flex"
 { STORE_POS; return KW_REGEXTEN;}
 	YY_BREAK
 case 26:
 YY_RULE_SETUP
-#line 244 "ael.flex"
+#line 247 "ael.flex"
 { STORE_POS; return KW_HINT;}
 	YY_BREAK
 case 27:
 YY_RULE_SETUP
-#line 245 "ael.flex"
+#line 248 "ael.flex"
 { STORE_POS; return KW_ELSE;}
 	YY_BREAK
 case 28:
 YY_RULE_SETUP
-#line 246 "ael.flex"
+#line 249 "ael.flex"
 { STORE_POS; return KW_GOTO;}
 	YY_BREAK
 case 29:
 YY_RULE_SETUP
-#line 247 "ael.flex"
+#line 250 "ael.flex"
 { STORE_POS; return KW_JUMP;}
 	YY_BREAK
 case 30:
 YY_RULE_SETUP
-#line 248 "ael.flex"
+#line 251 "ael.flex"
 { STORE_POS; return KW_RETURN;}
 	YY_BREAK
 case 31:
 YY_RULE_SETUP
-#line 249 "ael.flex"
+#line 252 "ael.flex"
 { STORE_POS; return KW_BREAK;}
 	YY_BREAK
 case 32:
 YY_RULE_SETUP
-#line 250 "ael.flex"
+#line 253 "ael.flex"
 { STORE_POS; return KW_CONTINUE;}
 	YY_BREAK
 case 33:
 YY_RULE_SETUP
-#line 251 "ael.flex"
+#line 254 "ael.flex"
 { STORE_POS; return KW_FOR;}
 	YY_BREAK
 case 34:
 YY_RULE_SETUP
-#line 252 "ael.flex"
+#line 255 "ael.flex"
 { STORE_POS; return KW_WHILE;}
 	YY_BREAK
 case 35:
 YY_RULE_SETUP
-#line 253 "ael.flex"
+#line 256 "ael.flex"
 { STORE_POS; return KW_CASE;}
 	YY_BREAK
 case 36:
 YY_RULE_SETUP
-#line 254 "ael.flex"
+#line 257 "ael.flex"
 { STORE_POS; return KW_DEFAULT;}
 	YY_BREAK
 case 37:
 YY_RULE_SETUP
-#line 255 "ael.flex"
+#line 258 "ael.flex"
 { STORE_POS; return KW_PATTERN;}
 	YY_BREAK
 case 38:
 YY_RULE_SETUP
-#line 256 "ael.flex"
+#line 259 "ael.flex"
 { STORE_POS; return KW_CATCH;}
 	YY_BREAK
 case 39:
 YY_RULE_SETUP
-#line 257 "ael.flex"
+#line 260 "ael.flex"
 { STORE_POS; return KW_SWITCHES;}
 	YY_BREAK
 case 40:
 YY_RULE_SETUP
-#line 258 "ael.flex"
+#line 261 "ael.flex"
 { STORE_POS; return KW_ESWITCHES;}
 	YY_BREAK
 case 41:
 YY_RULE_SETUP
-#line 259 "ael.flex"
+#line 262 "ael.flex"
 { STORE_POS; return KW_INCLUDES;}
 	YY_BREAK
 case 42:
 YY_RULE_SETUP
-#line 260 "ael.flex"
+#line 263 "ael.flex"
 { BEGIN(comment); my_col += 2; }
 	YY_BREAK
 case 43:
 YY_RULE_SETUP
-#line 262 "ael.flex"
+#line 265 "ael.flex"
 { my_col += yyleng; }
 	YY_BREAK
 case 44:
 /* rule 44 can match eol */
 YY_RULE_SETUP
-#line 263 "ael.flex"
+#line 266 "ael.flex"
 { ++my_lineno; my_col=1;}
 	YY_BREAK
 case 45:
 YY_RULE_SETUP
-#line 264 "ael.flex"
+#line 267 "ael.flex"
 { my_col += yyleng; }
 	YY_BREAK
 case 46:
 /* rule 46 can match eol */
 YY_RULE_SETUP
-#line 265 "ael.flex"
+#line 268 "ael.flex"
 { ++my_lineno; my_col=1;}
 	YY_BREAK
 case 47:
 YY_RULE_SETUP
-#line 266 "ael.flex"
+#line 269 "ael.flex"
 { my_col += 2; BEGIN(INITIAL); } /* the nice thing about comments is that you know exactly what ends them */
 	YY_BREAK
 case 48:
 /* rule 48 can match eol */
 YY_RULE_SETUP
-#line 268 "ael.flex"
+#line 271 "ael.flex"
 { my_lineno++; my_col = 1; }
 	YY_BREAK
 case 49:
 YY_RULE_SETUP
-#line 269 "ael.flex"
+#line 272 "ael.flex"
 { my_col += yyleng; }
 	YY_BREAK
 case 50:
 YY_RULE_SETUP
-#line 270 "ael.flex"
+#line 273 "ael.flex"
 { my_col += (yyleng*8)-(my_col%8); }
 	YY_BREAK
 case 51:
 YY_RULE_SETUP
-#line 272 "ael.flex"
+#line 275 "ael.flex"
 {
       /* boy did I open a can of worms when I changed the lexical token "word".
   	  	 all the above keywords can be used as a beginning to a "word".-
@@ -1596,22 +1580,22 @@ YY_RULE_SETUP
 	YY_BREAK
 case 52:
 YY_RULE_SETUP
-#line 298 "ael.flex"
+#line 301 "ael.flex"
 { yymore(); /* Keep going */ }
 	YY_BREAK
 case 53:
 YY_RULE_SETUP
-#line 299 "ael.flex"
+#line 302 "ael.flex"
 { yymore(); /* Keep going */ }
 	YY_BREAK
 case 54:
 YY_RULE_SETUP
-#line 300 "ael.flex"
+#line 303 "ael.flex"
 { yymore(); /* Keep Going */ }
 	YY_BREAK
 case 55:
 YY_RULE_SETUP
-#line 301 "ael.flex"
+#line 304 "ael.flex"
 { /* the beginning of a ${} construct. prepare and pop into curlystate */
 	   	parencount2 = 0;
 		pbcpos2 = 0;
@@ -1622,7 +1606,7 @@ YY_RULE_SETUP
 	YY_BREAK
 case 56:
 YY_RULE_SETUP
-#line 308 "ael.flex"
+#line 311 "ael.flex"
 { /* the beginning of a $[] construct. prepare and pop into brackstate */
 	   	parencount3 = 0;
 		pbcpos3 = 0;
@@ -1634,7 +1618,7 @@ YY_RULE_SETUP
 case 57:
 /* rule 57 can match eol */
 YY_RULE_SETUP
-#line 315 "ael.flex"
+#line 318 "ael.flex"
 {
 		/* a non-word constituent char, like a space, tab, curly, paren, etc */
 		char c = yytext[yyleng-1];
@@ -1650,7 +1634,7 @@ YY_RULE_SETUP
 case 58:
 /* rule 58 can match eol */
 YY_RULE_SETUP
-#line 328 "ael.flex"
+#line 331 "ael.flex"
 {
 		if ( pbcpop2('}') ) {	/* error */
 			STORE_LOC;
@@ -1673,7 +1657,7 @@ YY_RULE_SETUP
 case 59:
 /* rule 59 can match eol */
 YY_RULE_SETUP
-#line 347 "ael.flex"
+#line 350 "ael.flex"
 {
 		char c = yytext[yyleng-1];
 		if (c == '{')
@@ -1685,7 +1669,7 @@ YY_RULE_SETUP
 case 60:
 /* rule 60 can match eol */
 YY_RULE_SETUP
-#line 355 "ael.flex"
+#line 358 "ael.flex"
 {
 		char c = yytext[yyleng-1];
 		if ( pbcpop2(c))  { /* error */
@@ -1704,7 +1688,7 @@ YY_RULE_SETUP
 case 61:
 /* rule 61 can match eol */
 YY_RULE_SETUP
-#line 371 "ael.flex"
+#line 374 "ael.flex"
 {
 		if ( pbcpop3(']') ) {	/* error */
 			STORE_LOC;
@@ -1727,7 +1711,7 @@ YY_RULE_SETUP
 case 62:
 /* rule 62 can match eol */
 YY_RULE_SETUP
-#line 390 "ael.flex"
+#line 393 "ael.flex"
 {
 		char c = yytext[yyleng-1];
 		if (c == '[')
@@ -1739,7 +1723,7 @@ YY_RULE_SETUP
 case 63:
 /* rule 63 can match eol */
 YY_RULE_SETUP
-#line 398 "ael.flex"
+#line 401 "ael.flex"
 {
 		char c = yytext[yyleng-1];
 		if ( pbcpop3(c))  { /* error */
@@ -1765,7 +1749,7 @@ YY_RULE_SETUP
 case 64:
 /* rule 64 can match eol */
 YY_RULE_SETUP
-#line 421 "ael.flex"
+#line 424 "ael.flex"
 {
 		if ( pbcpop(')') ) {	/* error */
 			STORE_LOC;
@@ -1794,7 +1778,7 @@ YY_RULE_SETUP
 case 65:
 /* rule 65 can match eol */
 YY_RULE_SETUP
-#line 446 "ael.flex"
+#line 449 "ael.flex"
 {
 		char c = yytext[yyleng-1];
 		if (c == '(')
@@ -1806,7 +1790,7 @@ YY_RULE_SETUP
 case 66:
 /* rule 66 can match eol */
 YY_RULE_SETUP
-#line 454 "ael.flex"
+#line 457 "ael.flex"
 {
 		char c = yytext[yyleng-1];
 		if ( pbcpop(c))  { /* error */
@@ -1833,7 +1817,7 @@ YY_RULE_SETUP
 case 67:
 /* rule 67 can match eol */
 YY_RULE_SETUP
-#line 478 "ael.flex"
+#line 481 "ael.flex"
 {
 		char c = yytext[yyleng-1];
 		if (c == '(')
@@ -1845,7 +1829,7 @@ YY_RULE_SETUP
 case 68:
 /* rule 68 can match eol */
 YY_RULE_SETUP
-#line 486 "ael.flex"
+#line 489 "ael.flex"
 {
 		if ( pbcpop(')') ) { /* error */
 			STORE_LOC;
@@ -1876,7 +1860,7 @@ YY_RULE_SETUP
 case 69:
 /* rule 69 can match eol */
 YY_RULE_SETUP
-#line 513 "ael.flex"
+#line 516 "ael.flex"
 {
 		if( parencount != 0) { /* ast_log(LOG_NOTICE,"Folding in a comma!\n"); */
 			yymore();
@@ -1895,7 +1879,7 @@ YY_RULE_SETUP
 case 70:
 /* rule 70 can match eol */
 YY_RULE_SETUP
-#line 528 "ael.flex"
+#line 531 "ael.flex"
 {
 		char c = yytext[yyleng-1];
 		if ( pbcpop(c) ) { /* error */
@@ -1918,7 +1902,7 @@ YY_RULE_SETUP
 case 71:
 /* rule 71 can match eol */
 YY_RULE_SETUP
-#line 547 "ael.flex"
+#line 550 "ael.flex"
 {
 		char c = yytext[yyleng-1];
 		yymore();
@@ -1928,7 +1912,7 @@ YY_RULE_SETUP
 case 72:
 /* rule 72 can match eol */
 YY_RULE_SETUP
-#line 553 "ael.flex"
+#line 556 "ael.flex"
 {
 		char c = yytext[yyleng-1];
 		if ( pbcpop(c) ) { /* error */
@@ -1946,7 +1930,7 @@ YY_RULE_SETUP
 case 73:
 /* rule 73 can match eol */
 YY_RULE_SETUP
-#line 567 "ael.flex"
+#line 570 "ael.flex"
 {
 		STORE_LOC;
 		yylval->str = malloc(yyleng);
@@ -1960,7 +1944,7 @@ YY_RULE_SETUP
 case 74:
 /* rule 74 can match eol */
 YY_RULE_SETUP
-#line 577 "ael.flex"
+#line 580 "ael.flex"
 {
 		char fnamebuf[1024],*p1,*p2;
 		int glob_ret;
@@ -2013,7 +1997,7 @@ case YY_STATE_EOF(comment):
 case YY_STATE_EOF(curlystate):
 case YY_STATE_EOF(wordstate):
 case YY_STATE_EOF(brackstate):
-#line 622 "ael.flex"
+#line 625 "ael.flex"
 {
 		char fnamebuf[2048];
 		if (include_stack_index > 0 && include_stack[include_stack_index-1].globbuf_pos < include_stack[include_stack_index-1].globbuf.gl_pathc-1) {
@@ -2049,15 +2033,15 @@ case YY_STATE_EOF(brackstate):
 case 75:
 /* rule 75 can match eol */
 YY_RULE_SETUP
-#line 654 "ael.flex"
+#line 657 "ael.flex"
 { /* default rule */ ast_log(LOG_ERROR,"Unhandled char(s): %s\n", yytext); }
 	YY_BREAK
 case 76:
 YY_RULE_SETUP
-#line 656 "ael.flex"
+#line 659 "ael.flex"
 YY_FATAL_ERROR( "flex scanner jammed" );
 	YY_BREAK
-#line 2053 "ael_lex.c"
+#line 2042 "ael_lex.c"
 
 	case YY_END_OF_BUFFER:
 		{
@@ -2574,7 +2558,7 @@ static void ael_yy_load_buffer_state  (yyscan_t yyscanner)
     YY_BUFFER_STATE ael_yy_create_buffer  (FILE * file, int  size , yyscan_t yyscanner)
 {
 	YY_BUFFER_STATE b;
-
+    
 	b = (YY_BUFFER_STATE) ael_yyalloc(sizeof( struct yy_buffer_state ) ,yyscanner );
 	if ( ! b )
 		YY_FATAL_ERROR( "out of dynamic memory in ael_yy_create_buffer()" );
@@ -2618,7 +2602,7 @@ static void ael_yy_load_buffer_state  (yyscan_t yyscanner)
 #ifndef __cplusplus
 extern int isatty (int );
 #endif /* __cplusplus */
-
+    
 /* Initializes or reinitializes a buffer.
  * This function is sometimes called more than once on the same buffer,
  * such as during a ael_yyrestart() or at EOF.
@@ -2644,7 +2628,7 @@ extern int isatty (int );
     }
 
         b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;
-
+    
 	errno = oerrno;
 }
 
@@ -2750,9 +2734,9 @@ static void ael_yyensure_buffer_stack (yyscan_t yyscanner)
 								, yyscanner);
 		if ( ! yyg->yy_buffer_stack )
 			YY_FATAL_ERROR( "out of dynamic memory in ael_yyensure_buffer_stack()" );
-
+								  
 		memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*));
-
+				
 		yyg->yy_buffer_stack_max = num_to_alloc;
 		yyg->yy_buffer_stack_top = 0;
 		return;
@@ -2781,12 +2765,12 @@ static void ael_yyensure_buffer_stack (yyscan_t yyscanner)
  * @param base the character buffer
  * @param size the size in bytes of the character buffer
  * @param yyscanner The scanner object.
- * @return the newly allocated buffer state object.
+ * @return the newly allocated buffer state object. 
  */
 YY_BUFFER_STATE ael_yy_scan_buffer  (char * base, yy_size_t  size , yyscan_t yyscanner)
 {
 	YY_BUFFER_STATE b;
-
+    
 	if ( size < 2 ||
 	     base[size-2] != YY_END_OF_BUFFER_CHAR ||
 	     base[size-1] != YY_END_OF_BUFFER_CHAR )
@@ -2822,14 +2806,14 @@ YY_BUFFER_STATE ael_yy_scan_buffer  (char * base, yy_size_t  size , yyscan_t yys
  */
 YY_BUFFER_STATE ael_yy_scan_string (yyconst char * yystr , yyscan_t yyscanner)
 {
-
+    
 	return ael_yy_scan_bytes(yystr,strlen(yystr) ,yyscanner);
 }
 
 /** Setup the input buffer state to scan the given bytes. The next call to ael_yylex() will
  * scan from a @e copy of @a bytes.
- * @param yybytes the byte buffer to scan
- * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes.
+ * @param bytes the byte buffer to scan
+ * @param len the number of bytes in the buffer pointed to by @a bytes.
  * @param yyscanner The scanner object.
  * @return the newly allocated buffer state object.
  */
@@ -2839,7 +2823,7 @@ YY_BUFFER_STATE ael_yy_scan_bytes  (yyconst char * yybytes, int  _yybytes_len ,
 	char *buf;
 	yy_size_t n;
 	int i;
-
+    
 	/* Get memory for full buffer, including space for trailing EOB's. */
 	n = _yybytes_len + 2;
 	buf = (char *) ael_yyalloc(n ,yyscanner );
@@ -2907,10 +2891,10 @@ YY_EXTRA_TYPE ael_yyget_extra  (yyscan_t yyscanner)
 int ael_yyget_lineno  (yyscan_t yyscanner)
 {
     struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
-
+    
         if (! YY_CURRENT_BUFFER)
             return 0;
-
+    
     return yylineno;
 }
 
@@ -2920,10 +2904,10 @@ int ael_yyget_lineno  (yyscan_t yyscanner)
 int ael_yyget_column  (yyscan_t yyscanner)
 {
     struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
-
+    
         if (! YY_CURRENT_BUFFER)
             return 0;
-
+    
     return yycolumn;
 }
 
@@ -2984,13 +2968,13 @@ void ael_yyset_lineno (int  line_number , yyscan_t yyscanner)
 
         /* lineno is only valid if an input buffer exists. */
         if (! YY_CURRENT_BUFFER )
-           yy_fatal_error( "ael_yyset_lineno called with no buffer" , yyscanner);
-
+           yy_fatal_error( "ael_yyset_lineno called with no buffer" , yyscanner); 
+    
     yylineno = line_number;
 }
 
 /** Set the current column.
- * @param column_no
+ * @param line_number
  * @param yyscanner The scanner object.
  */
 void ael_yyset_column (int  column_no , yyscan_t yyscanner)
@@ -2999,8 +2983,8 @@ void ael_yyset_column (int  column_no , yyscan_t yyscanner)
 
         /* column is only valid if an input buffer exists. */
         if (! YY_CURRENT_BUFFER )
-           yy_fatal_error( "ael_yyset_column called with no buffer" , yyscanner);
-
+           yy_fatal_error( "ael_yyset_column called with no buffer" , yyscanner); 
+    
     yycolumn = column_no;
 }
 
@@ -3053,13 +3037,13 @@ YYLTYPE *ael_yyget_lloc  (yyscan_t yyscanner)
     struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
     return yylloc;
 }
-
+    
 void ael_yyset_lloc (YYLTYPE *  yylloc_param , yyscan_t yyscanner)
 {
     struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
     yylloc = yylloc_param;
 }
-
+    
 /* User-visible API */
 
 /* ael_yylex_init is special because it creates the scanner itself, so it is
@@ -3107,20 +3091,20 @@ int ael_yylex_init_extra(YY_EXTRA_TYPE yy_user_defined,yyscan_t* ptr_yy_globals
         errno = EINVAL;
         return 1;
     }
-
+	
     *ptr_yy_globals = (yyscan_t) ael_yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts );
-
+	
     if (*ptr_yy_globals == NULL){
         errno = ENOMEM;
         return 1;
     }
-
+    
     /* By setting to 0xAA, we expose bugs in
     yy_init_globals. Leave at 0x00 for releases. */
     memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t));
-
+    
     ael_yyset_extra (yy_user_defined, *ptr_yy_globals);
-
+    
     return yy_init_globals ( *ptr_yy_globals );
 }
 
@@ -3230,7 +3214,7 @@ void *ael_yyrealloc  (void * ptr, yy_size_t  size , yyscan_t yyscanner)
 
 #define YYTABLES_NAME "yytables"
 
-#line 656 "ael.flex"
+#line 659 "ael.flex"
 
 
 
@@ -3492,3 +3476,4 @@ static void setup_filestack(char *fnamebuf2, int fnamebuf_siz, glob_t *globbuf,
 		}
 	}
 }
+
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index 0bbbb195e2be4d8212f9d451acb1e0c58a7e0272..fffb87a1624f047c3afe8c71414d44a0c04a73b4 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -1043,6 +1043,7 @@ int ast_ari_validate_channel(struct ast_json *json)
 	int has_id = 0;
 	int has_language = 0;
 	int has_name = 0;
+	int has_protocol_id = 0;
 	int has_state = 0;
 
 	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
@@ -1135,6 +1136,16 @@ int ast_ari_validate_channel(struct ast_json *json)
 				res = 0;
 			}
 		} else
+		if (strcmp("protocol_id", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_protocol_id = 1;
+			prop_is_valid = ast_ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Channel field protocol_id failed validation\n");
+				res = 0;
+			}
+		} else
 		if (strcmp("state", ast_json_object_iter_key(iter)) == 0) {
 			int prop_is_valid;
 			has_state = 1;
@@ -1193,6 +1204,11 @@ int ast_ari_validate_channel(struct ast_json *json)
 		res = 0;
 	}
 
+	if (!has_protocol_id) {
+		ast_log(LOG_ERROR, "ARI Channel missing required field protocol_id\n");
+		res = 0;
+	}
+
 	if (!has_state) {
 		ast_log(LOG_ERROR, "ARI Channel missing required field state\n");
 		res = 0;
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index 583cba33a4e7cfb34e0c5a708a32c491c86eb0af..64f167c07cdc1dd20994636980ba16fd055c2a36 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -1353,6 +1353,7 @@ ari_validator ast_ari_validate_application_fn(void);
  * - id: string (required)
  * - language: string (required)
  * - name: string (required)
+ * - protocol_id: string (required)
  * - state: string (required)
  * Dialed
  * DialplanCEP
diff --git a/res/ari/cli.c b/res/ari/cli.c
index 9d0eb3099bc64f6251dadca20e32a17831bacd48..f9d9cecfb78d216c6d16e3bfc482847df3134ce6 100644
--- a/res/ari/cli.c
+++ b/res/ari/cli.c
@@ -60,13 +60,10 @@ static char *ari_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 	ast_cli(a->fd, "ARI Status:\n");
 	ast_cli(a->fd, "Enabled: %s\n", AST_CLI_YESNO(conf->general->enabled));
 	ast_cli(a->fd, "Output format: ");
-	switch (conf->general->format) {
-	case AST_JSON_COMPACT:
-		ast_cli(a->fd, "compact");
-		break;
-	case AST_JSON_PRETTY:
+	if (conf->general->format & AST_JSON_PRETTY) {
 		ast_cli(a->fd, "pretty");
-		break;
+	} else {
+		ast_cli(a->fd, "compact");
 	}
 	ast_cli(a->fd, "\n");
 	ast_cli(a->fd, "Auth realm: %s\n", conf->general->auth_realm);
diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c
index 71c4a14489fc3016855dfa4bc1f8ae659254f78c..9ee4a46e0b1b96a49ac3585d17087eff9bf57b87 100644
--- a/res/ari/resource_channels.c
+++ b/res/ari/resource_channels.c
@@ -1779,7 +1779,7 @@ void ast_ari_channels_create(struct ast_variable *headers,
 	struct ast_ari_channels_create_args *args,
 	struct ast_ari_response *response)
 {
-	struct ast_variable *variables = NULL;
+	RAII_VAR(struct ast_variable *, variables, NULL, ast_variables_destroy);
 	struct ast_assigned_ids assignedids;
 	struct ari_channel_thread_data *chan_data;
 	struct ast_channel_snapshot *snapshot;
diff --git a/res/parking/parking_applications.c b/res/parking/parking_applications.c
index 17ac4eb0ceb077bf84b0edd40d1b5b444c1c956b..7d11516a1ee8e852a6f249eef50d953141a50729 100644
--- a/res/parking/parking_applications.c
+++ b/res/parking/parking_applications.c
@@ -241,6 +241,7 @@
 enum park_args {
 	OPT_ARG_COMEBACK,
 	OPT_ARG_TIMEOUT,
+	OPT_ARG_MUSICONHOLD,
 	OPT_ARG_ARRAY_SIZE /* Always the last element of the enum */
 };
 
@@ -250,6 +251,7 @@ enum park_flags {
 	MUXFLAG_NOANNOUNCE = (1 << 2),
 	MUXFLAG_COMEBACK_OVERRIDE = (1 << 3),
 	MUXFLAG_TIMEOUT_OVERRIDE = (1 << 4),
+	MUXFLAG_MUSICONHOLD = (1 << 5),
 };
 
 AST_APP_OPTIONS(park_opts, {
@@ -258,6 +260,7 @@ AST_APP_OPTIONS(park_opts, {
 	AST_APP_OPTION('s', MUXFLAG_NOANNOUNCE),
 	AST_APP_OPTION_ARG('c', MUXFLAG_COMEBACK_OVERRIDE, OPT_ARG_COMEBACK),
 	AST_APP_OPTION_ARG('t', MUXFLAG_TIMEOUT_OVERRIDE, OPT_ARG_TIMEOUT),
+	AST_APP_OPTION_ARG('m', MUXFLAG_MUSICONHOLD, OPT_ARG_MUSICONHOLD),
 });
 
 static int apply_option_timeout (int *var, char *timeout_arg)
@@ -275,7 +278,8 @@ static int apply_option_timeout (int *var, char *timeout_arg)
 	return 0;
 }
 
-static int park_app_parse_data(const char *data, int *disable_announce, int *use_ringing, int *randomize, int *time_limit, char **comeback_override, char **lot_name)
+static int park_app_parse_data(const char *data, int *disable_announce, int *use_ringing, int *randomize, int *time_limit,
+	char **comeback_override, char **lot_name, char **musicclass)
 {
 	char *parse;
 	struct ast_flags flags = { 0 };
@@ -308,6 +312,10 @@ static int park_app_parse_data(const char *data, int *disable_announce, int *use
 			}
 		}
 
+		if (ast_test_flag(&flags, MUXFLAG_MUSICONHOLD)) {
+			*musicclass = ast_strdup(opts[OPT_ARG_MUSICONHOLD]);
+		}
+
 		if (ast_test_flag(&flags, MUXFLAG_RINGING)) {
 			*use_ringing = 1;
 		}
@@ -481,8 +489,8 @@ struct park_common_datastore *get_park_common_datastore_copy(struct ast_channel
 	return data_copy;
 }
 
-struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker,
-		const char *lot_name, const char *comeback_override,
+static struct ast_bridge *park_common_setup2(struct ast_channel *parkee, struct ast_channel *parker,
+		const char *lot_name, const char *comeback_override, const char *musicclass,
 		int use_ringing, int randomize, int time_limit, int silence_announcements)
 {
 	struct ast_bridge *parking_bridge;
@@ -518,11 +526,22 @@ struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_chan
 
 	/* Apply relevant bridge roles and such to the parking channel */
 	parking_channel_set_roles(parkee, lot, use_ringing);
+	/* If requested, override the MOH class */
+	if (!ast_strlen_zero(musicclass)) {
+		ast_channel_set_bridge_role_option(parkee, "holding_participant", "moh_class", musicclass);
+	}
 	setup_park_common_datastore(parkee, ast_channel_uniqueid(parker), comeback_override, randomize, time_limit,
 		silence_announcements);
 	return parking_bridge;
 }
 
+struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker,
+		const char *lot_name, const char *comeback_override,
+		int use_ringing, int randomize, int time_limit, int silence_announcements)
+{
+	return park_common_setup2(parkee, parker, lot_name, comeback_override, NULL, use_ringing, randomize, time_limit, silence_announcements);
+}
+
 struct ast_bridge *park_application_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *app_data,
 		int *silence_announcements)
 {
@@ -532,12 +551,13 @@ struct ast_bridge *park_application_setup(struct ast_channel *parkee, struct ast
 
 	RAII_VAR(char *, comeback_override, NULL, ast_free);
 	RAII_VAR(char *, lot_name_app_arg, NULL, ast_free);
+	RAII_VAR(char *, musicclass, NULL, ast_free);
 
 	if (app_data) {
-		park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &comeback_override, &lot_name_app_arg);
+		park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &comeback_override, &lot_name_app_arg, &musicclass);
 	}
 
-	return park_common_setup(parkee, parker, lot_name_app_arg, comeback_override, use_ringing,
+	return park_common_setup2(parkee, parker, lot_name_app_arg, comeback_override, musicclass, use_ringing,
 		randomize, time_limit, silence_announcements ? *silence_announcements : 0);
 
 }
diff --git a/res/parking/parking_controller.c b/res/parking/parking_controller.c
index 365acddb7f640756512669a9d6706c1c277589c4..9b789ceaec9d9d4ff96e36e6669f1795b21da2ef 100644
--- a/res/parking/parking_controller.c
+++ b/res/parking/parking_controller.c
@@ -110,6 +110,8 @@ int parking_lot_get_space(struct parking_lot *lot, int target_override)
 
 	if (target_override >= lot->cfg->parking_start && target_override <= lot->cfg->parking_stop) {
 		original_target = target_override;
+	} else if (target_override > -1) {
+		ast_log(LOG_WARNING, "Preferred parking spot %d is out of bounds (%d-%d)\n", target_override, lot->cfg->parking_start, lot->cfg->parking_stop);
 	}
 
 	current_target = original_target;
diff --git a/res/prometheus/bridges.c b/res/prometheus/bridges.c
index 5a916049e0126e973e7ca11235323278675ddb63..505dab839713ade109a25ccbceba1f9ad331556b 100644
--- a/res/prometheus/bridges.c
+++ b/res/prometheus/bridges.c
@@ -125,7 +125,17 @@ static void bridges_scrape_cb(struct ast_str **response)
 	/* Bridge dependent values */
 	it_bridges = ao2_iterator_init(bridges, 0);
 	for (i = 0; (bridge = ao2_iterator_next(&it_bridges)); ao2_ref(bridge, -1), i++) {
-		struct ast_bridge_snapshot *snapshot = ast_bridge_get_snapshot(bridge);
+		struct ast_bridge_snapshot *snapshot;
+
+		/* Invisible bridges don't get shown externally and have no snapshot */
+		if (ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_INVISIBLE)) {
+			continue;
+		}
+
+		snapshot = ast_bridge_get_snapshot(bridge);
+		if (!snapshot) {
+			continue;
+		}
 
 		for (j = 0; j < ARRAY_LEN(bridge_metric_defs); j++) {
 			int index = i * ARRAY_LEN(bridge_metric_defs) + j;
diff --git a/res/res.xml b/res/res.xml
index ace1792650deafe90f5a5fd834251f803739d195..0b34ec90628269d88cab279cd1e246924466e512 100644
--- a/res/res.xml
+++ b/res/res.xml
@@ -1,4 +1,4 @@
-<member name="res_digium_phone" displayname="Download the Digium Phone Module for Asterisk.  See http://downloads.digium.com/pub/telephony/res_digium_phone/README.">
+<member name="res_digium_phone" displayname="Download the Digium Phone Module for Asterisk.  See https://downloads.digium.com/pub/telephony/res_digium_phone/README.">
 	<support_level>external</support_level>
 	<conflict>no_binary_modules</conflict>
 	<depend>xmlstarlet</depend>
diff --git a/res/res_adsi.c b/res/res_adsi.c
index f8a2f5696c2e05a6250951e1b00bc54b653131fd..c273c00dd2403e0b6c97d0f0d618630a152e9067 100644
--- a/res/res_adsi.c
+++ b/res/res_adsi.c
@@ -157,7 +157,6 @@ static int adsi_careful_send(struct ast_channel *chan, unsigned char *buf, int l
 	struct ast_frame outf = {
 		.frametype = AST_FRAME_VOICE,
 		.subclass.format = ast_format_ulaw,
-		.data.ptr = buf,
 	};
 	int amt;
 
@@ -171,6 +170,7 @@ static int adsi_careful_send(struct ast_channel *chan, unsigned char *buf, int l
 			*remain = *remain - amt;
 		}
 
+		outf.data.ptr = buf;
 		outf.datalen = amt;
 		outf.samples = amt;
 		if (ast_write(chan, &outf)) {
@@ -211,6 +211,7 @@ static int adsi_careful_send(struct ast_channel *chan, unsigned char *buf, int l
 		} else if (remain) {
 			*remain = inf->datalen - amt;
 		}
+		outf.data.ptr = buf;
 		outf.datalen = amt;
 		outf.samples = amt;
 		if (ast_write(chan, &outf)) {
@@ -236,6 +237,7 @@ static int __adsi_transmit_messages(struct ast_channel *chan, unsigned char **ms
 
 	if (ast_channel_adsicpe(chan) == AST_ADSI_UNAVAILABLE) {
 		/* Don't bother if we know they don't support ADSI */
+		ast_log(LOG_WARNING, "ADSI is not supported for %s\n", ast_channel_name(chan));
 		errno = ENOSYS;
 		return -1;
 	}
@@ -255,7 +257,7 @@ static int __adsi_transmit_messages(struct ast_channel *chan, unsigned char **ms
 			for (;;) {
 				if (((res = ast_waitfor(chan, waittime)) < 1)) {
 					/* Didn't get back DTMF A in time */
-					ast_debug(1, "No ADSI CPE detected (%d)\n", res);
+					ast_verb(4, "No ADSI CPE detected (%d)\n", res);
 					if (!ast_channel_adsicpe(chan)) {
 						ast_channel_adsicpe_set(chan, AST_ADSI_UNAVAILABLE);
 					}
@@ -291,7 +293,7 @@ static int __adsi_transmit_messages(struct ast_channel *chan, unsigned char **ms
 				ast_frfree(f);
 			}
 
-			ast_debug(1, "ADSI Compatible CPE Detected\n");
+			ast_verb(4, "ADSI Compatible CPE Detected\n");
 		} else {
 			ast_debug(1, "Already in data mode\n");
 		}
diff --git a/res/res_aeap.c b/res/res_aeap.c
new file mode 100644
index 0000000000000000000000000000000000000000..e78956e20b7ed9454e110a95dad3248c22a45ecf
--- /dev/null
+++ b/res/res_aeap.c
@@ -0,0 +1,404 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Ben Ford <bford@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>res_http_websocket</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/astobj2.h"
+#include "asterisk/module.h"
+#include "asterisk/sorcery.h"
+#include "asterisk/cli.h"
+#include "asterisk/format.h"
+#include "asterisk/format_cap.h"
+#include "asterisk/res_aeap.h"
+
+#include "res_aeap/general.h"
+
+/*** DOCUMENTATION
+	<configInfo name="res_aeap" language="en_US">
+		<synopsis>Asterisk External Application Protocol (AEAP) module for Asterisk</synopsis>
+		<configFile name="aeap.conf">
+			<configObject name="client">
+				<synopsis>AEAP client options</synopsis>
+				<configOption name="type">
+					<synopsis>Must be of type 'client'.</synopsis>
+				</configOption>
+				<configOption name="url">
+					<synopsis>The URL of the server to connect to.</synopsis>
+				</configOption>
+				<configOption name="protocol">
+					<synopsis>The application protocol.</synopsis>
+				</configOption>
+				<configOption name="codecs">
+				        <synopsis>Optional media codec(s)</synopsis>
+					<description><para>
+					If this is specified, Asterisk will use this for codec related negotiations
+					with the external application. Otherwise, Asterisk will default to using the
+					codecs configured on the endpoint.
+					</para></description>
+				</configOption>
+			</configObject>
+		</configFile>
+	</configInfo>
+ ***/
+
+/* Asterisk External Application Protocol sorcery object */
+static struct ast_sorcery *aeap_sorcery;
+
+struct ast_sorcery *ast_aeap_sorcery(void) {
+	return aeap_sorcery;
+}
+
+struct ast_aeap_client_config
+{
+	SORCERY_OBJECT(details);
+	AST_DECLARE_STRING_FIELDS(
+		/*! The URL of the server to connect to */
+		AST_STRING_FIELD(url);
+		/*! The application protocol */
+		AST_STRING_FIELD(protocol);
+	);
+	/*! An optional list of codecs that will be used if provided */
+	struct ast_format_cap *codecs;
+};
+
+static void client_config_destructor(void *obj)
+{
+	struct ast_aeap_client_config *cfg = obj;
+
+	ast_string_field_free_memory(cfg);
+	ao2_cleanup(cfg->codecs);
+}
+
+static void *client_config_alloc(const char *name)
+{
+	struct ast_aeap_client_config *cfg;
+
+	cfg = ast_sorcery_generic_alloc(sizeof(*cfg), client_config_destructor);
+	if (!cfg) {
+		return NULL;
+	}
+
+	if (ast_string_field_init(cfg, 512)) {
+		ao2_ref(cfg, -1);
+		return NULL;
+	}
+
+	if (!(cfg->codecs = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
+		ao2_ref(cfg, -1);
+		return NULL;
+	}
+
+	return cfg;
+}
+
+static int client_config_apply(const struct ast_sorcery *sorcery, void *obj)
+{
+	struct ast_aeap_client_config *cfg = obj;
+
+	if (ast_strlen_zero(cfg->url)) {
+		ast_log(LOG_ERROR, "AEAP - URL must be present for '%s'\n", ast_sorcery_object_get_id(cfg));
+		return -1;
+	}
+
+	if (!ast_begins_with(cfg->url, "ws")) {
+		ast_log(LOG_ERROR, "AEAP - URL must be ws or wss for '%s'\n", ast_sorcery_object_get_id(cfg));
+		return -1;
+	}
+
+	return 0;
+}
+
+const struct ast_format_cap *ast_aeap_client_config_codecs(const struct ast_aeap_client_config *cfg)
+{
+	return cfg->codecs;
+}
+
+int ast_aeap_client_config_has_protocol(const struct ast_aeap_client_config *cfg,
+	const char *protocol)
+{
+	return !strcmp(protocol, cfg->protocol);
+}
+
+struct ao2_container *ast_aeap_client_configs_get(const char *protocol)
+{
+	struct ao2_container *container;
+	struct ast_variable *var;
+
+	var = protocol ? ast_variable_new("protocol ==", protocol, "") : NULL;
+
+	container = ast_sorcery_retrieve_by_fields(aeap_sorcery,
+		AEAP_CONFIG_CLIENT, AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, var);
+
+	ast_variables_destroy(var);
+
+	return container;
+}
+
+static struct ast_aeap_client_config *client_config_get(const char *id)
+{
+	return ast_sorcery_retrieve_by_id(aeap_sorcery, AEAP_CONFIG_CLIENT, id);
+}
+
+static char *aeap_tab_complete_name(const char *word, struct ao2_container *container)
+{
+	void *obj;
+	struct ao2_iterator it;
+	int wordlen = strlen(word);
+	int ret;
+
+	it = ao2_iterator_init(container, 0);
+	while ((obj = ao2_iterator_next(&it))) {
+		if (!strncasecmp(word, ast_sorcery_object_get_id(obj), wordlen)) {
+			ret = ast_cli_completion_add(ast_strdup(ast_sorcery_object_get_id(obj)));
+			if (ret) {
+				ao2_ref(obj, -1);
+				break;
+			}
+		}
+		ao2_ref(obj, -1);
+	}
+	ao2_iterator_destroy(&it);
+
+	ao2_ref(container, -1);
+
+	return NULL;
+}
+
+static int aeap_cli_show(void *obj, void *arg, int flags)
+{
+	struct ast_cli_args *a = arg;
+	struct ast_variable *options;
+	struct ast_variable *i;
+
+	if (!obj) {
+		ast_cli(a->fd, "No AEAP configuration found\n");
+		return 0;
+	}
+
+	options = ast_variable_list_sort(ast_sorcery_objectset_create(aeap_sorcery, obj));
+	if (!options) {
+		return 0;
+	}
+
+	ast_cli(a->fd, "%s: %s\n", ast_sorcery_object_get_type(obj),
+		ast_sorcery_object_get_id(obj));
+
+	for (i = options; i; i = i->next) {
+		ast_cli(a->fd, "\t%s: %s\n", i->name, i->value);
+	}
+
+	ast_cli(a->fd, "\n");
+
+	ast_variables_destroy(options);
+
+	return 0;
+}
+
+static char *client_config_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_aeap_client_config *cfg;
+
+	switch(cmd) {
+	case CLI_INIT:
+		e->command = "aeap show client";
+		e->usage =
+			"Usage: aeap show client <id>\n"
+			"       Show the AEAP settings for a given client\n";
+		return NULL;
+	case CLI_GENERATE:
+		if (a->pos == 3) {
+			return aeap_tab_complete_name(a->word, ast_aeap_client_configs_get(NULL));
+		} else {
+			return NULL;
+		}
+	}
+
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	cfg = client_config_get(a->argv[3]);
+	aeap_cli_show(cfg, a, 0);
+	ao2_cleanup(cfg);
+
+	return CLI_SUCCESS;
+}
+
+static char *client_config_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ao2_container *container;
+
+	switch(cmd) {
+	case CLI_INIT:
+		e->command = "aeap show clients";
+		e->usage =
+			"Usage: aeap show clients\n"
+			"       Show all configured AEAP clients\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 3) {
+		return CLI_SHOWUSAGE;
+	}
+
+	container = ast_aeap_client_configs_get(NULL);
+	if (!container || ao2_container_count(container) == 0) {
+		ast_cli(a->fd, "No AEAP clients found\n");
+		ao2_cleanup(container);
+		return CLI_SUCCESS;
+	}
+
+	ao2_callback(container, OBJ_NODATA, aeap_cli_show, a);
+	ao2_ref(container, -1);
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry aeap_cli[] = {
+	AST_CLI_DEFINE(client_config_show, "Show AEAP client configuration by id"),
+	AST_CLI_DEFINE(client_config_show_all, "Show all AEAP client configurations"),
+};
+
+static struct ast_aeap *aeap_create(const char *id,	const struct ast_aeap_params *params,
+	int connect, int timeout)
+{
+	struct ast_aeap_client_config *cfg;
+	struct ast_aeap *aeap;
+	const char *url = NULL;
+	const char *protocol = NULL;
+
+	cfg = client_config_get(id);
+	if (cfg) {
+		url = cfg->url;
+		protocol = cfg->protocol;
+	}
+
+#ifdef TEST_FRAMEWORK
+	else if (ast_begins_with(id, "_aeap_test_")) {
+		url = "ws://127.0.0.1:8088/ws";
+		protocol = id;
+	}
+#endif
+
+	if (!url && !protocol) {
+		ast_log(LOG_ERROR, "AEAP: unable to get configuration for '%s'\n", id);
+		return NULL;
+	}
+
+	aeap = connect ? ast_aeap_create_and_connect(url, params, url, protocol, timeout) :
+		ast_aeap_create(url, params);
+
+	ao2_cleanup(cfg);
+	return aeap;
+}
+
+struct ast_aeap *ast_aeap_create_by_id(const char *id, const struct ast_aeap_params *params)
+{
+	return aeap_create(id, params, 0, 0);
+}
+
+struct ast_aeap *ast_aeap_create_and_connect_by_id(const char *id,
+	const struct ast_aeap_params *params, int timeout)
+{
+	return aeap_create(id, params, 1, timeout);
+}
+
+struct ast_variable *ast_aeap_custom_fields_get(const char *id)
+{
+	struct ast_aeap_client_config *cfg;
+	struct ast_variable *vars;
+
+	cfg = client_config_get(id);
+	if (!cfg) {
+		ast_log(LOG_WARNING, "AEAP: no client configuration '%s' to get fields\n", id);
+		return NULL;
+	}
+
+	vars = ast_sorcery_objectset_create(aeap_sorcery, cfg);
+
+	ao2_ref(cfg, -1);
+	return vars;
+}
+
+static int reload_module(void)
+{
+	ast_sorcery_reload(aeap_sorcery);
+
+	return 0;
+}
+
+static int unload_module(void)
+{
+	ast_sorcery_unref(aeap_sorcery);
+	aeap_sorcery = NULL;
+
+	ast_cli_unregister_multiple(aeap_cli, ARRAY_LEN(aeap_cli));
+
+	aeap_general_finalize();
+
+	return 0;
+}
+
+static int load_module(void)
+{
+	if (aeap_general_initialize()) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	if (!(aeap_sorcery = ast_sorcery_open()))
+	{
+		ast_log(LOG_ERROR, "AEAP - failed to open sorcery\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sorcery_apply_default(aeap_sorcery, AEAP_CONFIG_CLIENT, "config", "aeap.conf,criteria=type=client");
+
+	if (ast_sorcery_object_register(aeap_sorcery, "client", client_config_alloc,
+		NULL, client_config_apply)) {
+		ast_log(LOG_ERROR, "AEAP - failed to register client sorcery object\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sorcery_object_field_register(aeap_sorcery, AEAP_CONFIG_CLIENT, "type", "", OPT_NOOP_T, 0, 0);
+	ast_sorcery_object_field_register(aeap_sorcery, AEAP_CONFIG_CLIENT, "url", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_aeap_client_config, url));
+	ast_sorcery_object_field_register(aeap_sorcery, AEAP_CONFIG_CLIENT, "protocol", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_aeap_client_config, protocol));
+	ast_sorcery_object_field_register(aeap_sorcery, AEAP_CONFIG_CLIENT, "codecs", "", OPT_CODEC_T, 1, FLDSET(struct ast_aeap_client_config, codecs));
+
+	ast_sorcery_load(aeap_sorcery);
+
+	ast_cli_register_multiple(aeap_cli, ARRAY_LEN(aeap_cli));
+
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER,
+	"Asterisk External Application Protocol Module for Asterisk",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.reload = reload_module,
+	.load_pri = AST_MODPRI_CHANNEL_DEPEND,
+	.requires = "res_http_websocket",
+);
diff --git a/res/res_aeap.exports.in b/res/res_aeap.exports.in
new file mode 100644
index 0000000000000000000000000000000000000000..c1fc5c4988505c157cc29bd2f1772a55f3735a69
--- /dev/null
+++ b/res/res_aeap.exports.in
@@ -0,0 +1,7 @@
+{
+	global:
+		LINKER_SYMBOL_PREFIXaeap_*;
+		LINKER_SYMBOL_PREFIXast_aeap_*;
+	local:
+		*;
+};
diff --git a/res/res_aeap/aeap.c b/res/res_aeap/aeap.c
new file mode 100644
index 0000000000000000000000000000000000000000..9094bbb3dbbfaacf3f19166812399a28f5147f32
--- /dev/null
+++ b/res/res_aeap/aeap.c
@@ -0,0 +1,501 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+
+#include <pthread.h>
+
+#include "asterisk/astobj2.h"
+#include "asterisk/strings.h"
+
+#include "asterisk/res_aeap.h"
+#include "asterisk/res_aeap_message.h"
+
+#include "logger.h"
+#include "transaction.h"
+#include "transport.h"
+
+#define AEAP_RECV_SIZE 32768
+
+struct aeap_user_data {
+	/*! The user data object */
+	void *obj;
+	/*! A user data identifier */
+	char id[0];
+};
+
+AO2_STRING_FIELD_HASH_FN(aeap_user_data, id);
+AO2_STRING_FIELD_CMP_FN(aeap_user_data, id);
+
+#define USER_DATA_BUCKETS 11
+
+struct ast_aeap {
+	/*! This object's configuration parameters */
+	const struct ast_aeap_params *params;
+	/*! Container for registered user data objects */
+	struct ao2_container *user_data;
+	/*! Transactions container */
+	struct ao2_container *transactions;
+	/*! Transport layer communicator */
+	struct aeap_transport *transport;
+	/*! Id of thread that reads data from the transport */
+	pthread_t read_thread_id;
+};
+
+static int tsx_end(void *obj, void *arg, int flags)
+{
+	aeap_transaction_end(obj, -1);
+
+	return 0;
+}
+
+static void aeap_destructor(void *obj)
+{
+	struct ast_aeap *aeap = obj;
+
+	/* Disconnect things first, which keeps transactions from further executing */
+	ast_aeap_disconnect(aeap);
+
+	aeap_transport_destroy(aeap->transport);
+
+	/*
+	 * Each contained transaction holds a pointer back to this transactions container,
+	 * which is removed upon transaction end. Thus by explicitly ending each transaction
+	 * here we can ensure all references to the transactions container are removed.
+	 */
+	ao2_callback(aeap->transactions, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE,
+			tsx_end, NULL);
+	ao2_cleanup(aeap->transactions);
+
+	ao2_cleanup(aeap->user_data);
+}
+
+struct ast_aeap *ast_aeap_create(const char *transport_type,
+	const struct ast_aeap_params *params)
+{
+	struct ast_aeap *aeap;
+
+	aeap = ao2_alloc(sizeof(*aeap), aeap_destructor);
+	if (!aeap) {
+		ast_log(LOG_ERROR, "AEAP: unable to create");
+		return NULL;
+	}
+
+	aeap->params = params;
+	aeap->read_thread_id = AST_PTHREADT_NULL;
+
+	aeap->user_data = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, USER_DATA_BUCKETS,
+		aeap_user_data_hash_fn, NULL, aeap_user_data_cmp_fn);
+	if (!aeap->user_data) {
+		aeap_error(aeap, NULL, "unable to create user data container");
+		ao2_ref(aeap, -1);
+		return NULL;
+	}
+
+	aeap->transactions = aeap_transactions_create();
+	if (!aeap->transactions) {
+		aeap_error(aeap, NULL, "unable to create transactions container");
+		ao2_ref(aeap, -1);
+		return NULL;
+	}
+
+	aeap->transport = aeap_transport_create(transport_type);
+	if (!aeap->transport) {
+		aeap_error(aeap, NULL, "unable to create transport");
+		ao2_ref(aeap, -1);
+		return NULL;
+	}
+
+	return aeap;
+}
+
+static struct aeap_user_data *aeap_user_data_create(const char *id, void *obj,
+	ast_aeap_user_obj_cleanup cleanup)
+{
+	struct aeap_user_data *data;
+
+	ast_assert(id != NULL);
+
+	data = ao2_t_alloc_options(sizeof(*data) + strlen(id) + 1, cleanup,
+		AO2_ALLOC_OPT_LOCK_NOLOCK, "");
+	if (!data) {
+		if (cleanup) {
+			cleanup(obj);
+		}
+
+		return NULL;
+	}
+
+	strcpy(data->id, id); /* safe */
+	data->obj = obj;
+
+	return data;
+}
+
+int ast_aeap_user_data_register(struct ast_aeap *aeap, const char *id, void *obj,
+	ast_aeap_user_obj_cleanup cleanup)
+{
+	struct aeap_user_data *data;
+
+	data = aeap_user_data_create(id, obj, cleanup);
+	if (!data) {
+		return -1;
+	}
+
+	if (!ao2_link(aeap->user_data, data)) {
+		ao2_ref(data, -1);
+		return -1;
+	}
+
+	ao2_ref(data, -1);
+	return 0;
+}
+
+void ast_aeap_user_data_unregister(struct ast_aeap *aeap, const char *id)
+{
+	ao2_find(aeap->user_data, id, OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NODATA);
+}
+
+void *ast_aeap_user_data_object_by_id(struct ast_aeap *aeap, const char *id)
+{
+	struct aeap_user_data *data;
+	void *obj;
+
+	data = ao2_find(aeap->user_data, id, OBJ_SEARCH_KEY);
+	if (!data) {
+		return NULL;
+	}
+
+	obj = data->obj;
+	ao2_ref(data, -1);
+
+	/*
+	 * Returned object's lifetime is based on how it was registered.
+	 * See public function docs for more info
+	 */
+	return obj;
+}
+
+static int raise_msg_handler(struct ast_aeap *aeap,	const struct ast_aeap_message_handler *handlers,
+	size_t size, struct ast_aeap_message *msg, void *data)
+{
+	ast_aeap_on_message on_message = NULL;
+	size_t i;
+
+	if (!aeap->params->emit_error) {
+		const char *error_msg = ast_aeap_message_error_msg(msg);
+
+		if (error_msg) {
+			aeap_error(aeap, NULL, "%s", error_msg);
+			return -1;
+		}
+
+		/* If no error_msg then it's assumed this is not an error message */
+	}
+
+	for (i = 0; i < size; ++i) {
+		if (ast_strlen_zero(handlers[i].name)) {
+			/* A default handler is specified. Use it if no other match is found */
+			on_message = handlers[i].on_message;
+			continue;
+		}
+
+		if (ast_aeap_message_is_named(msg, handlers[i].name)) {
+			on_message = handlers[i].on_message;
+			break;
+		}
+	}
+
+	if (on_message) {
+		return on_message(aeap, msg, data);
+	}
+
+	/* Respond with un-handled error */
+	ast_aeap_send_msg(aeap, ast_aeap_message_create_error(aeap->params->msg_type,
+		ast_aeap_message_name(msg), ast_aeap_message_id(msg),
+		"Unsupported and/or un-handled message"));
+
+	return 0;
+}
+
+static void raise_msg(struct ast_aeap *aeap, const void *buf, intmax_t size,
+	enum AST_AEAP_DATA_TYPE serial_type)
+{
+	struct ast_aeap_message *msg;
+	struct aeap_transaction *tsx;
+	int res = 0;
+
+	if (!aeap->params || !aeap->params->msg_type ||
+		ast_aeap_message_serial_type(aeap->params->msg_type) != serial_type ||
+		!(msg = ast_aeap_message_deserialize(aeap->params->msg_type, buf, size))) {
+		return;
+	}
+
+	/* See if this msg is involved in a transaction */
+	tsx = aeap_transaction_get(aeap->transactions, ast_aeap_message_id(msg));
+
+	/* If so go ahead and cancel the timeout timer */
+	aeap_transaction_cancel_timer(tsx);
+
+	if (aeap->params->request_handlers && ast_aeap_message_is_request(msg)) {
+		res = raise_msg_handler(aeap, aeap->params->request_handlers, aeap->params->request_handlers_size,
+			msg, tsx ? aeap_transaction_user_obj(tsx) : NULL);
+	} else if (aeap->params->response_handlers && ast_aeap_message_is_response(msg)) {
+		res = raise_msg_handler(aeap, aeap->params->response_handlers, aeap->params->response_handlers_size,
+			msg, tsx ? aeap_transaction_user_obj(tsx) : NULL);
+	}
+
+	/* Complete transaction (Note, removes tsx ref) */
+	aeap_transaction_end(tsx, res);
+
+	ao2_ref(msg, -1);
+}
+
+static void *aeap_receive(void *data)
+{
+	struct ast_aeap *aeap = data;
+	void *buf;
+
+	buf = ast_calloc(1, AEAP_RECV_SIZE);
+	if (!buf) {
+		aeap_error(aeap, NULL, "unable to create read buffer");
+		goto aeap_receive_error;
+	}
+
+	while (aeap_transport_is_connected(aeap->transport)) {
+		enum AST_AEAP_DATA_TYPE rtype;
+		intmax_t size;
+
+		size = aeap_transport_read(aeap->transport, buf, AEAP_RECV_SIZE, &rtype);
+		if (size < 0) {
+			goto aeap_receive_error;
+		}
+
+		if (!size) {
+			continue;
+		}
+
+		switch (rtype) {
+		case AST_AEAP_DATA_TYPE_BINARY:
+			if (aeap->params && aeap->params->on_binary) {
+				aeap->params->on_binary(aeap, buf, size);
+			}
+			break;
+		case AST_AEAP_DATA_TYPE_STRING:
+			ast_debug(3, "AEAP: received message: %s\n", (char *)buf);
+			if (aeap->params && aeap->params->on_string) {
+				aeap->params->on_string(aeap, (const char *)buf, size - 1);
+			}
+			break;
+		default:
+			break;
+		}
+
+		raise_msg(aeap, buf, size, rtype);
+	};
+
+	ast_free(buf);
+	return NULL;
+
+aeap_receive_error:
+	/*
+	 * An unrecoverable error occurred so ensure the aeap and transport reset
+	 * to a disconnected state. We don't want this thread to "join" itself so set
+	 * its id to NULL prior to disconnecting.
+	 */
+	aeap_error(aeap, NULL, "unrecoverable read error, disconnecting");
+
+	ao2_lock(aeap);
+	aeap->read_thread_id = AST_PTHREADT_NULL;
+	ao2_unlock(aeap);
+
+	ast_aeap_disconnect(aeap);
+
+	ast_free(buf);
+
+	if (aeap->params && aeap->params->on_error) {
+		aeap->params->on_error(aeap);
+	}
+
+	return NULL;
+}
+
+int ast_aeap_connect(struct ast_aeap *aeap, const char *url, const char *protocol, int timeout)
+{
+	SCOPED_AO2LOCK(lock, aeap);
+
+	if (aeap_transport_is_connected(aeap->transport)) {
+		/* Should already be connected, so nothing to do */
+		return 0;
+	}
+
+	if (aeap_transport_connect(aeap->transport, url, protocol, timeout)) {
+		aeap_error(aeap, NULL, "unable to connect transport");
+		return -1;
+	}
+
+	if (ast_pthread_create_background(&aeap->read_thread_id, NULL,
+			aeap_receive, aeap)) {
+		aeap_error(aeap, NULL, "unable to start read thread: %s",
+			strerror(errno));
+		ast_aeap_disconnect(aeap);
+		return -1;
+	}
+
+	return 0;
+}
+
+struct ast_aeap *ast_aeap_create_and_connect(const char *type,
+	const struct ast_aeap_params *params, const char *url, const char *protocol, int timeout)
+{
+	struct ast_aeap *aeap;
+
+	aeap = ast_aeap_create(type, params);
+	if (!aeap) {
+		return NULL;
+	}
+
+	if (ast_aeap_connect(aeap, url, protocol, timeout)) {
+		ao2_ref(aeap, -1);
+		return NULL;
+	}
+
+	return aeap;
+}
+
+int ast_aeap_disconnect(struct ast_aeap *aeap)
+{
+	ao2_lock(aeap);
+
+	aeap_transport_disconnect(aeap->transport);
+
+	if (aeap->read_thread_id != AST_PTHREADT_NULL) {
+		/*
+		 * The read thread calls disconnect if an error occurs, so
+		 * unlock the aeap before "joining" to avoid a deadlock.
+		 */
+		ao2_unlock(aeap);
+		pthread_join(aeap->read_thread_id, NULL);
+		ao2_lock(aeap);
+
+		aeap->read_thread_id = AST_PTHREADT_NULL;
+	}
+
+	ao2_unlock(aeap);
+
+	return 0;
+}
+
+static int aeap_send(struct ast_aeap *aeap, const void *buf, uintmax_t size,
+	enum AST_AEAP_DATA_TYPE type)
+{
+	intmax_t num;
+
+	num = aeap_transport_write(aeap->transport, buf, size, type);
+
+	if (num == 0) {
+		/* Nothing written, could be disconnected */
+		return 0;
+	}
+
+	if (num < 0) {
+		aeap_error(aeap, NULL, "error sending data");
+		return -1;
+	}
+
+	if (num < size) {
+		aeap_error(aeap, NULL, "not all data sent");
+		return -1;
+	}
+
+	if (num > size) {
+		aeap_error(aeap, NULL, "sent data truncated");
+		return -1;
+	}
+
+	return 0;
+}
+
+int ast_aeap_send_binary(struct ast_aeap *aeap, const void *buf, uintmax_t size)
+{
+	return aeap_send(aeap, buf, size, AST_AEAP_DATA_TYPE_BINARY);
+}
+
+int ast_aeap_send_msg(struct ast_aeap *aeap, struct ast_aeap_message *msg)
+{
+	void *buf;
+	intmax_t size;
+	int res;
+
+	if (!msg) {
+		aeap_error(aeap, NULL, "no message to send");
+		return -1;
+	}
+
+	if (ast_aeap_message_serialize(msg, &buf, &size)) {
+		aeap_error(aeap, NULL, "unable to serialize outgoing message");
+		ao2_ref(msg, -1);
+		return -1;
+	}
+
+	res = aeap_send(aeap, buf, size, msg->type->serial_type);
+
+	ast_free(buf);
+	ao2_ref(msg, -1);
+
+	return res;
+}
+
+int ast_aeap_send_msg_tsx(struct ast_aeap *aeap, struct ast_aeap_tsx_params *params)
+{
+	struct aeap_transaction *tsx = NULL;
+	int res = 0;
+
+	if (!params) {
+		return -1;
+	}
+
+	if (!params->msg) {
+		aeap_transaction_params_cleanup(params);
+		aeap_error(aeap, NULL, "no message to send");
+		return -1;
+	}
+
+	/* The transaction will take over params cleanup, which includes the msg reference */
+	tsx = aeap_transaction_create_and_add(aeap->transactions,
+		ast_aeap_message_id(params->msg), params, aeap);
+	if (!tsx) {
+		return -1;
+	}
+
+	if (ast_aeap_send_msg(aeap, ao2_bump(params->msg))) {
+		aeap_transaction_end(tsx, -1); /* Removes container, and tsx ref */
+		return -1;
+	}
+
+	if (aeap_transaction_start(tsx)) {
+		aeap_transaction_end(tsx, -1); /* Removes container, and tsx ref */
+		return -1;
+	}
+
+	res = aeap_transaction_result(tsx);
+
+	ao2_ref(tsx, -1);
+
+	return res;
+}
diff --git a/res/res_aeap/general.c b/res/res_aeap/general.c
new file mode 100644
index 0000000000000000000000000000000000000000..7bd180703fb4de4cdf82774936781b97561e57ef
--- /dev/null
+++ b/res/res_aeap/general.c
@@ -0,0 +1,58 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+
+#include "asterisk/astobj2.h"
+#include "asterisk/sched.h"
+
+#include "general.h"
+
+/*! \brief Scheduler for transaction timeouts */
+static struct ast_sched_context *sched = NULL;
+
+struct ast_sched_context *aeap_sched_context(void)
+{
+	return sched;
+}
+
+void aeap_general_finalize(void)
+{
+	if (sched) {
+		ast_sched_context_destroy(sched);
+		sched = NULL;
+	}
+}
+
+int aeap_general_initialize(void)
+{
+	sched = ast_sched_context_create();
+	if (!sched) {
+		ast_log(LOG_ERROR, "AEAP scheduler: unable to create context");
+		return -1;
+	}
+
+	if (ast_sched_start_thread(sched)) {
+		ast_log(LOG_ERROR, "AEAP scheduler: unable to start thread");
+		aeap_general_finalize();
+		return -1;
+	}
+
+	return 0;
+}
+
diff --git a/res/res_aeap/general.h b/res/res_aeap/general.h
new file mode 100644
index 0000000000000000000000000000000000000000..52a092b43c3dd79f8a05bf83ca787e99cc03735e
--- /dev/null
+++ b/res/res_aeap/general.h
@@ -0,0 +1,41 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef RES_AEAP_GENERAL_H
+#define RES_AEAP_GENERAL_H
+
+/*!
+ * \brief Retrieve the scheduling context
+ *
+ * \returns The scheduling context
+ */
+struct ast_sched_context *aeap_sched_context(void);
+
+/*!
+ * \brief Initialize general/common AEAP facilities
+ *
+ * \returns 0 on success, -1 on error
+ */
+int aeap_general_initialize(void);
+
+/*!
+ * \brief Finalize/cleanup general AEAP facilities
+ */
+void aeap_general_finalize(void);
+
+#endif /* RES_AEAP_GENERAL_H */
diff --git a/res/res_aeap/logger.h b/res/res_aeap/logger.h
new file mode 100644
index 0000000000000000000000000000000000000000..db264c357fe84dc267ed3d8887c14fe5ec71bf60
--- /dev/null
+++ b/res/res_aeap/logger.h
@@ -0,0 +1,60 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef RES_AEAP_LOGGER_H
+#define RES_AEAP_LOGGER_H
+
+#include "asterisk.h"
+
+#include "asterisk/logger.h"
+#include "asterisk/strings.h"
+
+/*!
+ * \brief Log an Asterisk external application message
+ *
+ * \param level The logging level
+ * \param obj The object being logged
+ * \param name Optional subsystem name
+ * \param fmt Format string
+ * \param ... Parameters for the format string
+ */
+#define aeap_log(level, obj, name, fmt, ...) \
+	ast_log(level, "AEAP%s%s (%p): " fmt "\n", ast_strlen_zero(name) ? "" : " ", \
+			ast_strlen_zero(name) ? "" : name, obj, ##__VA_ARGS__)
+
+/*!
+ * \brief Log an Asterisk external application error
+ *
+ * \param obj The object being logged
+ * \param name Optional subsystem name
+ * \param fmt Format string
+ * \param ... Parameters for the format string
+ */
+#define aeap_error(obj, name, fmt, ...) aeap_log(LOG_ERROR, obj, name, fmt, ##__VA_ARGS__)
+
+/*!
+ * \brief Log an Asterisk external application warning
+ *
+ * \param obj The object being logged
+ * \param name Optional subsystem name
+ * \param fmt Format string
+ * \param ... Parameters for the format string
+ */
+#define aeap_warn(obj, name, fmt, ...) aeap_log(LOG_WARNING, obj, name, fmt, ##__VA_ARGS__)
+
+#endif /* RES_AEAP_LOGGER_H */
diff --git a/res/res_aeap/message.c b/res/res_aeap/message.c
new file mode 100644
index 0000000000000000000000000000000000000000..826e93b664ee3cc4a739aaf08978db93d3ed1a43
--- /dev/null
+++ b/res/res_aeap/message.c
@@ -0,0 +1,270 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+
+#include "asterisk/astobj2.h"
+#include "asterisk/utils.h"
+#include "asterisk/uuid.h"
+#include "asterisk/res_aeap.h"
+#include "asterisk/res_aeap_message.h"
+
+enum AST_AEAP_DATA_TYPE ast_aeap_message_serial_type(const struct ast_aeap_message_type *type)
+{
+	ast_assert(type != NULL);
+
+	return type->serial_type;
+}
+
+static void message_destructor(void *obj)
+{
+	struct ast_aeap_message *msg = obj;
+
+	if (msg->type->destruct) {
+		msg->type->destruct(msg);
+	}
+}
+
+static struct ast_aeap_message *message_create(const struct ast_aeap_message_type *type)
+{
+	struct ast_aeap_message *msg;
+
+	msg = ao2_t_alloc_options(type->type_size, message_destructor,
+		AO2_ALLOC_OPT_LOCK_NOLOCK, type->type_name);
+	if (!msg) {
+		ast_log(LOG_ERROR, "AEAP message %s: unable to create\n", type->type_name);
+		return NULL;
+	}
+
+	msg->type = type;
+
+	return msg;
+}
+
+struct ast_aeap_message *ast_aeap_message_create1(const struct ast_aeap_message_type *type,
+	const void *params)
+{
+	struct ast_aeap_message *msg;
+
+	ast_assert(type != NULL);
+	ast_assert(type->construct1 != NULL);
+
+	msg = message_create(type);
+	if (!msg) {
+		return NULL;
+	}
+
+	if (type->construct1(msg, params)) {
+		ast_log(LOG_ERROR, "AEAP message %s: unable to construct1\n", type->type_name);
+		ao2_ref(msg, -1);
+		return NULL;
+	}
+
+	return msg;
+}
+
+struct ast_aeap_message *ast_aeap_message_create2(const struct ast_aeap_message_type *type,
+	const char *msg_type, const char *name, const char *id, const void *params)
+{
+	struct ast_aeap_message *msg;
+
+	ast_assert(type != NULL);
+	ast_assert(type->construct2 != NULL);
+	ast_assert(msg_type != NULL);
+	ast_assert(name != NULL);
+
+	msg = message_create(type);
+	if (!msg) {
+		return NULL;
+	}
+
+	if (type->construct2(msg, msg_type, name, id, params)) {
+		ast_log(LOG_ERROR, "AEAP message %s: unable to construct2\n", type->type_name);
+		ao2_ref(msg, -1);
+		return NULL;
+	}
+
+	return msg;
+}
+
+struct ast_aeap_message *ast_aeap_message_create_request(const struct ast_aeap_message_type *type,
+	const char *name, const char *id, const void *params)
+{
+	struct ast_aeap_message *msg;
+
+	msg = ast_aeap_message_create2(type, "request", name, id, params);
+	if (!msg) {
+		return NULL;
+	}
+
+	if (!id && !ast_aeap_message_id_generate(msg)) {
+		ao2_ref(msg, -1);
+		return NULL;
+	}
+
+	return msg;
+}
+
+struct ast_aeap_message *ast_aeap_message_create_response(const struct ast_aeap_message_type *type,
+	const char *name, const char *id, const void *params)
+{
+	return ast_aeap_message_create2(type, "response", name, id, params);
+}
+
+struct ast_aeap_message *ast_aeap_message_create_error(const struct ast_aeap_message_type *type,
+	const char *name, const char *id, const char *error_msg)
+{
+	struct ast_aeap_message *msg;
+
+	msg = ast_aeap_message_create_response(type, name, id, NULL);
+	if (!msg) {
+		return NULL;
+	}
+
+	if (ast_aeap_message_error_msg_set(msg, error_msg)) {
+		ao2_ref(msg, -1);
+		return NULL;
+	}
+
+	return msg;
+}
+
+struct ast_aeap_message *ast_aeap_message_deserialize(const struct ast_aeap_message_type *type,
+	const void *buf, intmax_t size)
+{
+	struct ast_aeap_message *msg;
+
+	ast_assert(type != NULL);
+	ast_assert(type->deserialize != NULL);
+
+	msg = ast_aeap_message_create1(type, NULL);
+	if (!msg) {
+		return NULL;
+	}
+
+	if (type->deserialize(msg, buf, size)) {
+		ao2_ref(msg, -1);
+		return NULL;
+	}
+
+	return msg;
+}
+
+int ast_aeap_message_serialize(const struct ast_aeap_message *message,
+	void **buf, intmax_t *size)
+{
+	ast_assert(message != NULL);
+	ast_assert(message->type != NULL);
+
+	return message->type->serialize ? message->type->serialize(message, buf, size) : 0;
+}
+
+const char *ast_aeap_message_id(const struct ast_aeap_message *message)
+{
+	const char *id = NULL;
+
+	ast_assert(message != NULL);
+	ast_assert(message->type != NULL);
+
+	if (message->type->id) {
+		id = message->type->id(message);
+	}
+
+	return id ? id : "";
+}
+
+int ast_aeap_message_id_set(struct ast_aeap_message *message, const char *id)
+{
+	ast_assert(message != NULL);
+	ast_assert(message->type != NULL);
+
+	return message->type->id_set ? message->type->id_set(message, id) : 0;
+}
+
+const char *ast_aeap_message_id_generate(struct ast_aeap_message *message)
+{
+	char uuid_str[AST_UUID_STR_LEN];
+
+	ast_uuid_generate_str(uuid_str, sizeof(uuid_str));
+	if (strlen(uuid_str) != (AST_UUID_STR_LEN - 1)) {
+		ast_log(LOG_ERROR, "AEAP message %s failed to generate UUID for message '%s'",
+			message->type->type_name, ast_aeap_message_name(message));
+		return NULL;
+	}
+
+	return ast_aeap_message_id_set(message, uuid_str) ? NULL : ast_aeap_message_id(message);
+}
+
+const char *ast_aeap_message_name(const struct ast_aeap_message *message)
+{
+	const char *name = NULL;
+
+	ast_assert(message != NULL);
+	ast_assert(message->type != NULL);
+
+	if (message->type->name) {
+		name = message->type->name(message);
+	}
+
+	return name ? name : "";
+}
+
+int ast_aeap_message_is_named(const struct ast_aeap_message *message, const char *name)
+{
+	return name ? !strcasecmp(ast_aeap_message_name(message), name) : 0;
+}
+
+void *ast_aeap_message_data(struct ast_aeap_message *message)
+{
+	ast_assert(message != NULL);
+	ast_assert(message->type != NULL);
+
+	return message->type->data ? message->type->data(message) : NULL;
+}
+
+int ast_aeap_message_is_request(const struct ast_aeap_message *message)
+{
+	ast_assert(message != NULL);
+	ast_assert(message->type != NULL);
+
+	return message->type->is_request ? message->type->is_request(message) : 0;
+}
+
+int ast_aeap_message_is_response(const struct ast_aeap_message *message)
+{
+	ast_assert(message != NULL);
+	ast_assert(message->type != NULL);
+
+	return message->type->is_response ? message->type->is_response(message) : 0;
+}
+
+const char *ast_aeap_message_error_msg(const struct ast_aeap_message *message)
+{
+	ast_assert(message != NULL);
+	ast_assert(message->type != NULL);
+
+	return message->type->error_msg ? message->type->error_msg(message) : NULL;
+}
+
+int ast_aeap_message_error_msg_set(struct ast_aeap_message *message, const char *error_msg)
+{
+	ast_assert(message != NULL);
+	ast_assert(message->type != NULL);
+
+	return message->type->error_msg_set ? message->type->error_msg_set(message, error_msg) : 0;
+}
diff --git a/res/res_aeap/message_json.c b/res/res_aeap/message_json.c
new file mode 100644
index 0000000000000000000000000000000000000000..f5cfe9d46134ca268ec01a9cace756c087d461f9
--- /dev/null
+++ b/res/res_aeap/message_json.c
@@ -0,0 +1,191 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+
+#include "asterisk/json.h"
+
+#include "asterisk/res_aeap_message.h"
+
+#define JSON_MSG(lvalue, rvalue) struct message_json *lvalue = \
+		((struct message_json *)rvalue)
+
+/*!
+ * \brief Asterisk external application JSON message type
+ */
+struct message_json {
+	/*! The base message type (must be first) */
+	struct ast_aeap_message base;
+	/*! Underlying JSON data structure */
+	struct ast_json *json;
+};
+
+static int message_json_construct1(struct ast_aeap_message *self, const void *params)
+{
+	JSON_MSG(msg, self);
+
+	msg->json = ast_json_ref((struct ast_json *)params) ?: ast_json_object_create();
+
+	return msg->json ? 0 : -1;
+}
+
+static int message_json_construct2(struct ast_aeap_message *self, const char *msg_type,
+	const char *name, const char *id, const void *params)
+{
+	struct ast_json *msg_data;
+	int res;
+
+	msg_data = ast_json_pack("{s:s,s:s*}", msg_type, name, "id", id);
+
+	if (!msg_data) {
+		ast_log(LOG_ERROR, "AEAP message json: failed to create data for '%s: %s'", msg_type, name);
+		return -1;
+	}
+
+	if (params && ast_json_object_update(msg_data, (struct ast_json *)params)) {
+		ast_log(LOG_ERROR, "AEAP message json: failed to update data for '%s: %s'", msg_type, name);
+		ast_json_unref(msg_data);
+		return -1;
+	}
+
+	res = message_json_construct1(self, msg_data);
+	ast_json_unref(msg_data);
+	return res;
+}
+
+static void message_json_destruct(struct ast_aeap_message *self)
+{
+	JSON_MSG(msg, self);
+
+	ast_json_unref(msg->json);
+}
+
+static int message_json_deserialize(struct ast_aeap_message *self, const void *buf, intmax_t size)
+{
+	JSON_MSG(msg, self);
+
+	msg->json = ast_json_load_buf(buf, size, NULL);
+
+	return msg->json ? 0 : -1;
+}
+
+static int message_json_serialize(const struct ast_aeap_message *self, void **buf, intmax_t *size)
+{
+	const JSON_MSG(msg, self);
+
+	*buf = ast_json_dump_string(msg->json);
+	if (!*buf) {
+		*size = 0;
+		return -1;
+	}
+
+	*size = strlen(*buf);
+
+	return 0;
+}
+
+static const char *message_json_id(const struct ast_aeap_message *self)
+{
+	const JSON_MSG(msg, self);
+
+	return ast_json_object_string_get(msg->json, "id");
+}
+
+static int message_json_id_set(struct ast_aeap_message *self, const char *id)
+{
+	JSON_MSG(msg, self);
+
+	if (ast_json_object_set(msg->json, "id", ast_json_string_create(id))) {
+		return -1;
+	}
+
+	return 0;
+}
+
+static const char *message_json_name(const struct ast_aeap_message *self)
+{
+	const JSON_MSG(msg, self);
+	struct ast_json_iter *iter;
+
+	iter = ast_json_object_iter_at(msg->json, "response");
+	if (!iter) {
+		iter = ast_json_object_iter_at(msg->json, "request");
+	}
+
+	return iter ? ast_json_string_get(ast_json_object_iter_value(iter)) : "";
+}
+
+static void *message_json_data(struct ast_aeap_message *self)
+{
+	JSON_MSG(msg, self);
+
+	return msg->json;
+}
+
+static int message_json_is_request(const struct ast_aeap_message *self)
+{
+	const JSON_MSG(msg, self);
+
+	return ast_json_object_iter_at(msg->json, "request") != NULL;
+}
+
+static int message_json_is_response(const struct ast_aeap_message *self)
+{
+	const JSON_MSG(msg, self);
+
+	return ast_json_object_iter_at(msg->json, "response") != NULL;
+}
+
+static const char *message_json_error_msg(const struct ast_aeap_message *self)
+{
+	const JSON_MSG(msg, self);
+
+	return ast_json_object_string_get(msg->json, "error_msg");
+}
+
+static int message_json_error_msg_set(struct ast_aeap_message *self, const char *error_msg)
+{
+	JSON_MSG(msg, self);
+
+	if (ast_json_object_set(msg->json, "error_msg", ast_json_string_create(error_msg))) {
+		return -1;
+	}
+
+	return 0;
+}
+
+static const struct ast_aeap_message_type message_type_json = {
+	.type_size = sizeof(struct message_json),
+	.type_name = "json",
+	.serial_type = AST_AEAP_DATA_TYPE_STRING,
+	.construct1 = message_json_construct1,
+	.construct2 = message_json_construct2,
+	.destruct = message_json_destruct,
+	.deserialize = message_json_deserialize,
+	.serialize = message_json_serialize,
+	.id = message_json_id,
+	.id_set = message_json_id_set,
+	.name = message_json_name,
+	.data = message_json_data,
+	.is_request = message_json_is_request,
+	.is_response = message_json_is_response,
+	.error_msg = message_json_error_msg,
+	.error_msg_set = message_json_error_msg_set,
+};
+
+const struct ast_aeap_message_type *ast_aeap_message_type_json = &message_type_json;
diff --git a/res/res_aeap/transaction.c b/res/res_aeap/transaction.c
new file mode 100644
index 0000000000000000000000000000000000000000..3f42cb294bddda551aec4aa4c33ac22faf8dba5c
--- /dev/null
+++ b/res/res_aeap/transaction.c
@@ -0,0 +1,284 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+
+#include "asterisk/astobj2.h"
+#include "asterisk/sched.h"
+#include "asterisk/utils.h"
+
+#include "asterisk/res_aeap.h"
+#include "asterisk/res_aeap_message.h"
+
+#include "general.h"
+#include "logger.h"
+#include "transaction.h"
+
+struct aeap_transaction {
+	/*! Pointer back to owner object */
+	struct ast_aeap *aeap;
+	/*! The container this transaction is in */
+	struct ao2_container *container;
+	/*! Scheduler ID message timeout */
+	int sched_id;
+	/*! Whether or not the handler has been executed */
+	int handled;
+	/*! Used to sync matching received messages */
+	ast_cond_t handled_cond;
+	/*! The result of this transaction */
+	int result;
+	/*! The timeout data */
+	struct ast_aeap_tsx_params params;
+	/*! The transaction identifier */
+	char id[0];
+};
+
+/*! \brief Number of transaction buckets */
+#define AEAP_TRANSACTION_BUCKETS 11
+
+AO2_STRING_FIELD_HASH_FN(aeap_transaction, id);
+AO2_STRING_FIELD_CMP_FN(aeap_transaction, id);
+
+int aeap_transaction_cancel_timer(struct aeap_transaction *tsx)
+{
+	if (tsx && tsx->sched_id != -1) {
+		AST_SCHED_DEL_UNREF(aeap_sched_context(), tsx->sched_id, ao2_ref(tsx, -1));
+		return tsx->sched_id != -1;
+	}
+
+	return 0;
+}
+
+void aeap_transaction_params_cleanup(struct ast_aeap_tsx_params *params)
+{
+	ao2_cleanup(params->msg);
+
+	if (params->obj_cleanup) {
+		params->obj_cleanup(params->obj);
+	}
+}
+
+static void transaction_destructor(void *obj)
+{
+	struct aeap_transaction *tsx = obj;
+
+	/* Ensure timer is canceled */
+	aeap_transaction_cancel_timer(tsx);
+
+	aeap_transaction_params_cleanup(&tsx->params);
+
+	ast_cond_destroy(&tsx->handled_cond);
+}
+
+static struct aeap_transaction *transaction_create(const char *id,
+	struct ast_aeap_tsx_params *params, struct ast_aeap *aeap)
+{
+	struct aeap_transaction *tsx;
+
+	if (!id) {
+		aeap_error(aeap, "transaction", "missing transaction id");
+		aeap_transaction_params_cleanup(params);
+		return NULL;
+	}
+
+	tsx = ao2_alloc(sizeof(*tsx) + strlen(id) + 1, transaction_destructor);
+	if (!tsx) {
+		aeap_error(aeap, "transaction", "unable to create for '%s'", id);
+		aeap_transaction_params_cleanup(params);
+		return NULL;
+	}
+
+	strcpy(tsx->id, id); /* safe */
+	tsx->sched_id = -1;
+
+	ast_cond_init(&tsx->handled_cond, NULL);
+
+	/*
+	 * Currently, transactions, and their lifetimes are fully managed by the given 'aeap'
+	 * object, so do not bump its reference here as we want the 'aeap' object to stop
+	 * transactions and not transactions potentially stopping the 'aeap' object.
+	 */
+	tsx->aeap = aeap;
+	tsx->params = *params;
+
+	return tsx;
+}
+
+static void transaction_end(struct aeap_transaction *tsx, int timed_out, int result)
+{
+	if (!tsx) {
+		return;
+	}
+
+	ao2_lock(tsx);
+
+	tsx->result = result;
+
+	if (tsx->container) {
+		ao2_unlink(tsx->container, tsx);
+		tsx->container = NULL;
+	}
+
+	if (!timed_out) {
+		aeap_transaction_cancel_timer(tsx);
+	} else if (tsx->sched_id != -1) {
+		tsx->sched_id = -1;
+	}
+
+	if (!tsx->handled) {
+		if (timed_out) {
+			if (tsx->params.on_timeout) {
+				tsx->params.on_timeout(tsx->aeap, tsx->params.msg, tsx->params.obj);
+			} else {
+				aeap_error(tsx->aeap, "transaction", "message '%s' timed out",
+					ast_aeap_message_name(tsx->params.msg));
+			}
+		}
+
+		tsx->handled = 1;
+		ast_cond_signal(&tsx->handled_cond);
+	}
+
+	ao2_unlock(tsx);
+
+	ao2_ref(tsx, -1);
+}
+
+static int transaction_raise_timeout(const void *data)
+{
+	/* Ref added added at timer creation removed in end call */
+	transaction_end((struct aeap_transaction *)data, 1, -1);
+
+	return 0;
+}
+
+static int transaction_sched_timer(struct aeap_transaction *tsx)
+{
+	if (tsx->params.timeout <= 0 || tsx->sched_id != -1) {
+		return 0;
+	}
+
+	tsx->sched_id = ast_sched_add(aeap_sched_context(), tsx->params.timeout,
+			transaction_raise_timeout, ao2_bump(tsx));
+	if (tsx->sched_id == -1) {
+		aeap_error(tsx->aeap, "transaction", "unable to schedule timeout for '%s'", tsx->id);
+		ao2_ref(tsx, -1);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void transaction_wait(struct aeap_transaction *tsx)
+{
+	ao2_lock(tsx);
+
+	while (!tsx->handled) {
+		ast_cond_wait(&tsx->handled_cond, ao2_object_get_lockaddr(tsx));
+	}
+
+	ao2_unlock(tsx);
+}
+
+int aeap_transaction_start(struct aeap_transaction *tsx)
+{
+	if (transaction_sched_timer(tsx)) {
+		return -1;
+	}
+
+	if (tsx->params.wait) {
+		/* Wait until transaction completes, or times out */
+		transaction_wait(tsx);
+	}
+
+	return 0;
+}
+
+struct aeap_transaction *aeap_transaction_get(struct ao2_container *transactions, const char *id)
+{
+	return ao2_find(transactions, id, OBJ_SEARCH_KEY);
+}
+
+void aeap_transaction_end(struct aeap_transaction *tsx, int result)
+{
+	transaction_end(tsx, 0, result);
+}
+
+int aeap_transaction_result(struct aeap_transaction *tsx)
+{
+	return tsx->result;
+}
+
+void *aeap_transaction_user_obj(struct aeap_transaction *tsx)
+{
+	return tsx->params.obj;
+}
+
+struct aeap_transaction *aeap_transaction_create_and_add(struct ao2_container *transactions,
+	const char *id, struct ast_aeap_tsx_params *params, struct ast_aeap *aeap)
+{
+	struct aeap_transaction *tsx;
+
+	tsx = transaction_create(id, params, aeap);
+	if (!tsx) {
+		return NULL;
+	}
+
+	if (!ao2_link(transactions, tsx)) {
+		aeap_error(tsx->aeap, "transaction", "unable to add '%s' to container", id);
+		ao2_ref(tsx, -1);
+		return NULL;
+	}
+
+	/*
+	 * Yes, this creates a circular reference. This reference is removed though
+	 * upon transaction end. It's assumed here that the given transactions container
+	 * takes "ownership", and ultimate responsibility of its contained transactions.
+	 * Thus when the given container needs to be unref'ed/freed it must call
+	 * aeap_transaction_end for each transaction prior to doing so.
+	 */
+	/* tsx->container = ao2_bump(transactions); */
+
+	/*
+	 * The transaction needs to know what container manages it, so it can remove
+	 * itself from the given container under certain conditions (e.g. transaction
+	 * timeout).
+	 *
+	 * It's expected that the given container will out live any contained transaction
+	 * (i.e. the container will not itself be destroyed before ensuring all contained
+	 * transactions are ended, and removed). Thus there is no reason to bump the given
+	 * container's reference here.
+	 */
+	tsx->container = transactions;
+
+	return tsx;
+}
+
+struct ao2_container *aeap_transactions_create(void)
+{
+	struct ao2_container *transactions;
+
+	transactions = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, AEAP_TRANSACTION_BUCKETS,
+		aeap_transaction_hash_fn, NULL, aeap_transaction_cmp_fn);
+	if (!transactions) {
+		ast_log(LOG_ERROR, "AEAP transaction: unable to create container\n");
+		return NULL;
+	}
+
+	return transactions;
+}
diff --git a/res/res_aeap/transaction.h b/res/res_aeap/transaction.h
new file mode 100644
index 0000000000000000000000000000000000000000..6e484119ae4e2cc677b94fe5f8413bb4bec7aa5c
--- /dev/null
+++ b/res/res_aeap/transaction.h
@@ -0,0 +1,123 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef RES_AEAP_TRANSACTION_H
+#define RES_AEAP_TRANSACTION_H
+
+#include "asterisk/res_aeap.h"
+
+struct ao2_container;
+struct ast_aeap_tsx_params;
+struct aeap_transaction;
+
+/*!
+ * \brief Create an Asterisk external application transactions container
+ *
+ * \returns A transaction object, or NULL on error
+ */
+struct ao2_container *aeap_transactions_create(void);
+
+/*!
+ * \brief Create a transaction object, and add it to the given container
+ *
+ * \param transactions A transactions container
+ * \param id An id to use for the transaction
+ * \param params Transaction parameters
+ * \param aeap The aeap object that "owns" this transaction
+ *
+ * \returns 0 if successfully create and added, -1 on error
+ */
+struct aeap_transaction *aeap_transaction_create_and_add(struct ao2_container *transactions,
+	const char *id, struct ast_aeap_tsx_params *params, struct ast_aeap *aeap);
+
+/*!
+ * \brief Clean up parameter references, and possibly call optional user object cleanup
+ *
+ * \param params Transaction parameters
+ */
+void aeap_transaction_params_cleanup(struct ast_aeap_tsx_params *params);
+
+/*!
+ * \brief Retrieve a transaction for the id from the container
+ *
+ * \param transactions A transactions container
+ * \param id A transaction id
+ *
+ * \returns an AEAP transaction object, NULL if no transaction is found
+ */
+struct aeap_transaction *aeap_transaction_get(struct ao2_container *transactions,
+	const char *id);
+
+/*!
+ * \brief Start the transaction
+ *
+ * \param tsx The transaction to initiate
+ *
+ * \returns 0 if successfully raised, and handled. Otherwise non zero.
+ */
+int aeap_transaction_start(struct aeap_transaction *tsx);
+
+/*!
+ * \brief End a transaction, and remove it from the given container
+ *
+ * The "result" parameter is a value representing the state (success/failure,
+ * perhaps even something else) of transactional processing upon ending.
+ *
+ * \param tsx A transaction to end
+ * \param result A result to give to the transaction
+ */
+void aeap_transaction_end(struct aeap_transaction *tsx, int result);
+
+/*!
+ * \brief Get a transaction's result
+ *
+ * A transaction's result is a value that represents the relative success (0), or
+ * failure (-1) of a transaction. For example, a timeout is considered a failure
+ * and will elicit a -1.
+ *
+ * This value though is also dependent upon the result of the message handler
+ * associated with the transaction. Meaning if an associated message is handled,
+ * then its result is stored as the transaction result and returned here.
+ *
+ * \param tsx A transaction object
+ *
+ * \returns The transaction result
+ */
+int aeap_transaction_result(struct aeap_transaction *tsx);
+
+/*!
+ * \brief Cancel the transaction timer
+ *
+ * Stops the transaction timer, but does not end/stop the transaction itself
+ *
+ * \param tsx A transaction to cancel the timer on
+ *
+ * \returns 0 if canceled, non zero otherwise
+ */
+int aeap_transaction_cancel_timer(struct aeap_transaction *tsx);
+
+/*!
+ * \brief Retrieve the user object associated with the transaction
+ *
+ * \param tsx A transaction object
+ *
+ * \returns A user object, or NULL if non associated
+ */
+void *aeap_transaction_user_obj(struct aeap_transaction *tsx);
+
+#endif /* RES_AEAP_TRANSACTION_H */
diff --git a/res/res_aeap/transport.c b/res/res_aeap/transport.c
new file mode 100644
index 0000000000000000000000000000000000000000..c032e10ee37c3a0f59de5d19d1a2ffebe81dac6d
--- /dev/null
+++ b/res/res_aeap/transport.c
@@ -0,0 +1,156 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+
+#include "asterisk/utils.h"
+
+#include "logger.h"
+#include "transport.h"
+#include "transport_websocket.h"
+
+struct aeap_transport *aeap_transport_create(const char *type)
+{
+	struct aeap_transport *transport = NULL;
+
+	if (!strncasecmp(type, "ws", 2)) {
+		transport = (struct aeap_transport *)aeap_transport_websocket_create();
+	}
+
+	if (!transport) {
+		ast_log(LOG_ERROR, "AEAP transport: failed to create for type '%s'\n", type);
+		return NULL;
+	}
+
+	ast_mutex_init(&transport->read_lock);
+	ast_mutex_init(&transport->write_lock);
+
+	transport->connected = 0;
+
+	return transport;
+}
+
+int aeap_transport_connect(struct aeap_transport *transport, const char *url,
+	const char *protocol, int timeout)
+{
+	int res;
+
+	SCOPED_MUTEX(rlock, &transport->read_lock);
+	SCOPED_MUTEX(wlock, &transport->write_lock);
+
+	if (aeap_transport_is_connected(transport)) {
+		return 0;
+	}
+
+	res = transport->vtable->connect(transport, url, protocol, timeout);
+	if (!res) {
+		transport->connected = 1;
+	}
+
+	return res;
+}
+
+struct aeap_transport *aeap_transport_create_and_connect(const char *type,
+	const char *url, const char *protocol, int timeout)
+{
+	struct aeap_transport *transport = aeap_transport_create(type);
+
+	if (!transport) {
+		return NULL;
+	}
+
+	if (aeap_transport_connect(transport, url, protocol, timeout)) {
+		aeap_transport_destroy(transport);
+		return NULL;
+	}
+
+	return transport;
+}
+
+int aeap_transport_is_connected(struct aeap_transport *transport)
+{
+	/*
+	 * Avoid using a lock to 'read' the 'connected' variable in order to
+	 * keep things slightly more efficient.
+	 */
+	return ast_atomic_fetch_add(&transport->connected, 0, __ATOMIC_RELAXED);
+}
+
+int aeap_transport_disconnect(struct aeap_transport *transport)
+{
+	int res;
+
+	SCOPED_MUTEX(rlock, &transport->read_lock);
+	SCOPED_MUTEX(wlock, &transport->write_lock);
+
+	if (!aeap_transport_is_connected(transport)) {
+		return 0;
+	}
+
+	res = transport->vtable->disconnect(transport);
+
+	/*
+	 * Even though the transport is locked here use atomics to set the value of
+	 * 'connected' since it's possible the variable is being 'read' by another
+	 * thread via the 'is_connected' call.
+	 */
+	ast_atomic_fetch_sub(&transport->connected, 1, __ATOMIC_RELAXED);
+
+	return res;
+}
+
+void aeap_transport_destroy(struct aeap_transport *transport)
+{
+	if (!transport) {
+		return;
+	}
+
+	/* Ensure an orderly disconnect occurs before final destruction */
+	aeap_transport_disconnect(transport);
+
+	transport->vtable->destroy(transport);
+
+	ast_mutex_destroy(&transport->read_lock);
+	ast_mutex_destroy(&transport->write_lock);
+
+	ast_free(transport);
+}
+
+intmax_t aeap_transport_read(struct aeap_transport *transport, void *buf, intmax_t size,
+	enum AST_AEAP_DATA_TYPE *rtype)
+{
+	SCOPED_MUTEX(lock, &transport->read_lock);
+
+	if (!aeap_transport_is_connected(transport)) {
+		return 0;
+	}
+
+	return transport->vtable->read(transport, buf, size, rtype);
+}
+
+intmax_t aeap_transport_write(struct aeap_transport *transport, const void *buf, intmax_t size,
+	enum AST_AEAP_DATA_TYPE wtype)
+{
+	SCOPED_MUTEX(lock, &transport->write_lock);
+
+	if (!aeap_transport_is_connected(transport)) {
+		return 0;
+	}
+
+	return transport->vtable->write(transport, buf, size, wtype);
+}
diff --git a/res/res_aeap/transport.h b/res/res_aeap/transport.h
new file mode 100644
index 0000000000000000000000000000000000000000..1cd4997e19cca50b7a4910b175c034bf1e2dec89
--- /dev/null
+++ b/res/res_aeap/transport.h
@@ -0,0 +1,207 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef RES_AEAP_TRANSPORT_H
+#define RES_AEAP_TRANSPORT_H
+
+#include <stdint.h>
+
+#include "asterisk/res_aeap.h"
+
+struct aeap_transport;
+
+/*!
+ * \brief Asterisk external application transport virtual table
+ *
+ * Callbacks to be implemented by "derived" transports
+ */
+struct aeap_transport_vtable {
+	/*!
+	 * \brief Connect a transport
+	 *
+	 * \param self The transport object
+	 * \param url The URL to connect to
+	 * \param protocol The connection protocol to use if applicable
+	 * \param timeout How long (in milliseconds) to attempt to connect (-1 equals infinite)
+	 *
+	 * \returns 0 on success, or -1 on error
+	 */
+	int (*connect)(struct aeap_transport *self, const char *url, const char *protocol, int timeout);
+
+	/*!
+	 * \brief Disconnect a transport
+	 *
+	 * \param self The transport object
+	 *
+	 * \returns 0 on success, or -1 on error
+	 */
+	int (*disconnect)(struct aeap_transport *self);
+
+	/*!
+	 * \brief Destroy a transport
+	 *
+	 * \param self The transport object
+	 */
+	void (*destroy)(struct aeap_transport *self);
+
+	/*!
+	 * \brief Read data from a transport
+	 *
+	 * \param self The transport object
+	 * \param buf The buffer data is read read into
+	 * \param size The size of the given data buffer
+	 * \param rtype [out] The type of data read
+	 *
+	 * \returns Total number of bytes read, or less than zero on error
+	 */
+	intmax_t (*read)(struct aeap_transport *self, void *buf, intmax_t size,
+		enum AST_AEAP_DATA_TYPE *rtype);
+
+	/*!
+	 * \brief Write data to a transport
+	 *
+	 * \param self The transport object
+	 * \param buf The data to write
+	 * \param size The size of data to write
+	 * \param wtype The type of data to write
+	 *
+	 * \returns Total number of bytes written, or less than zero on error
+	 */
+	intmax_t (*write)(struct aeap_transport *self, const void *buf, intmax_t size,
+		enum AST_AEAP_DATA_TYPE wtype);
+};
+
+/*!
+ * \brief Asterisk external application transport structure to be
+ *        "derived" by specific transport implementation types
+ *
+ * Transports are assumed to support simultaneous reading and writing,
+ * thus separate read and write locks. A transport type not supporting
+ * such can simply apply the opposing lock during a read or write, i.e.
+ * lock the write lock during a read and vice versa.
+ */
+struct aeap_transport {
+	/*! Transport virtual table */
+	struct aeap_transport_vtable *vtable;
+	/*! Whether or not the transport is connected */
+	unsigned int connected;
+	/*! Lock used when reading */
+	ast_mutex_t read_lock;
+	/*! Lock used when writing */
+	ast_mutex_t write_lock;
+};
+
+/*!
+ * \brief Create an Asterisk external application transport
+ *
+ * \param type The type of transport to create
+ *
+ * \returns An Asterisk external application transport, or NULL on error
+ */
+struct aeap_transport *aeap_transport_create(const char *type);
+
+/*!
+ * \brief Connect a transport
+ *
+ * \param transport The transport to connect
+ * \param url The URL to connect to
+ * \param protocol The connection protocol to use if applicable
+ * \param timeout How long (in milliseconds) to attempt to connect (-1 equals infinite)
+ *
+ * \returns 0 on success, or -1 on error
+ */
+int aeap_transport_connect(struct aeap_transport *transport, const char *url,
+	const char *protocol, int timeout);
+
+/*!
+ * \brief Create an Asterisk external application transport, and connect it
+ *
+ * \param type The type of transport to create
+ * \param url The URL to connect to
+ * \param protocol The connection protocol to use if applicable
+ * \param timeout How long (in milliseconds) to attempt to connect (-1 equals infinite)
+ *
+ * \returns An Asterisk external application transport, or NULL on error
+ */
+struct aeap_transport *aeap_transport_create_and_connect(const char* type,
+	const char *url, const char *protocol, int timeout);
+
+/*!
+ * \brief Disconnect a transport
+ *
+ * \note Locks both the transport's read and write locks before calling transport
+ *       instance's disconnect, and unlocks both before returning.
+ *
+ * \param transport The transport to disconnect
+ *
+ * \returns 0 on success, or -1 on error
+ */
+int aeap_transport_disconnect(struct aeap_transport *transport);
+
+/*!
+ * \brief Whether or not the transport is in a connected state
+ *
+ * \param transport The transport object
+ *
+ * \returns True if connected, false otherwise
+ */
+int aeap_transport_is_connected(struct aeap_transport *transport);
+
+/*!
+ * \brief Destroy a transport
+ *
+ * \param transport The transport to destroy
+ */
+void aeap_transport_destroy(struct aeap_transport *transport);
+
+/*!
+ * \brief Read data from the transport
+ *
+ * This is a blocking read, and will not return until the transport
+ * implementation returns.
+ *
+ * \note Locks transport's read lock before calling transport instance's
+ *       read, and unlocks it before returning.
+ *
+ * \param transport The transport to read from
+ * \param buf The buffer data is read into
+ * \param size The size of data given data buffer
+ * \param rtype [out] The type of data read
+ *
+ * \returns Total number of bytes read, or less than zero on error
+ */
+intmax_t aeap_transport_read(struct aeap_transport *transport, void *buf, intmax_t size,
+	enum AST_AEAP_DATA_TYPE *rtype);
+
+/*!
+ * \brief Write data to the transport
+ *
+ * \note Locks transport's write lock before calling transport instance's
+ *       write, and unlocks it before returning.
+ *
+ * \param transport The transport to write to
+ * \param buf The data to write
+ * \param size The size of data to write
+ * \param wtype The type of data to write
+ *
+ * \returns Total number of bytes written, or less than zero on error
+ */
+intmax_t aeap_transport_write(struct aeap_transport *transport, const void *buf, intmax_t size,
+	enum AST_AEAP_DATA_TYPE wtype);
+
+#endif /* RES_AEAP_TRANSPORT_H */
diff --git a/res/res_aeap/transport_websocket.c b/res/res_aeap/transport_websocket.c
new file mode 100644
index 0000000000000000000000000000000000000000..801e4d82a08e7caff14bb36c071da8b756161033
--- /dev/null
+++ b/res/res_aeap/transport_websocket.c
@@ -0,0 +1,254 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+
+#include "asterisk/http_websocket.h"
+#include "asterisk/utils.h"
+
+#include "logger.h"
+#include "transport.h"
+#include "transport_websocket.h"
+
+#define log_error(obj, fmt, ...) aeap_error(obj, "websocket", fmt, ##__VA_ARGS__)
+
+struct aeap_transport_websocket {
+	/*! Derive from base transport (must be first attribute) */
+	struct aeap_transport base;
+	/*! The underlying websocket */
+	struct ast_websocket *ws;
+};
+
+static int websocket_connect(struct aeap_transport *self, const char *url,
+	const char *protocol, int timeout)
+{
+	struct aeap_transport_websocket *transport = (struct aeap_transport_websocket *)self;
+	enum ast_websocket_result ws_result;
+	struct ast_websocket_client_options ws_options = {
+		.uri = url,
+		.protocols = protocol,
+		.timeout = timeout,
+		.tls_cfg = NULL,
+	};
+
+	transport->ws = ast_websocket_client_create_with_options(&ws_options, &ws_result);
+	if (ws_result != WS_OK) {
+		log_error(self, "connect failure (%d)", (int)ws_result);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int websocket_disconnect(struct aeap_transport *self)
+{
+	struct aeap_transport_websocket *transport = (struct aeap_transport_websocket *)self;
+
+	if (transport->ws) {
+		ast_websocket_unref(transport->ws);
+		transport->ws = NULL;
+	}
+
+	return 0;
+}
+
+static void websocket_destroy(struct aeap_transport *self)
+{
+	/*
+	 * Disconnect takes care of cleaning up the websocket. Note, disconnect
+	 * was called by the base/dispatch interface prior to calling this
+	 * function so nothing to do here.
+	 */
+}
+
+static intmax_t websocket_read(struct aeap_transport *self, void *buf, intmax_t size,
+	enum AST_AEAP_DATA_TYPE *rtype)
+{
+	struct aeap_transport_websocket *transport = (struct aeap_transport_websocket *)self;
+
+	char *payload;
+	uint64_t bytes_read = 0;
+	uint64_t total_bytes_read = 0;
+	enum ast_websocket_opcode opcode;
+	int fragmented = 0;
+
+	*rtype = AST_AEAP_DATA_TYPE_NONE;
+
+	if (ast_websocket_fd(transport->ws) < 0) {
+		log_error(self, "unavailable for reading");
+		/* Ensure this transport is in a disconnected state */
+		aeap_transport_disconnect(self);
+		return -1;
+	}
+
+	/*
+	 * This function is called with the read_lock locked. However, the lock needs to be
+	 * unlocked while waiting for input otherwise a deadlock can occur during disconnect
+	 * (disconnect attempts to grab the lock but can't because read holds it here). So
+	 * unlock it prior to waiting.
+	 */
+	ast_mutex_unlock(&transport->base.read_lock);
+	while (ast_websocket_wait_for_input(transport->ws, -1) <= 0) {
+		/* If this was poll getting interrupted just go back to waiting */
+		if (errno == EINTR || errno == EAGAIN) {
+			continue;
+		}
+
+		ast_mutex_lock(&transport->base.read_lock);
+		log_error(self, "poll failure: %s", strerror(errno));
+		/* Ensure this transport is in a disconnected state */
+		aeap_transport_disconnect(self);
+		return -1;
+	}
+	ast_mutex_lock(&transport->base.read_lock);
+
+	if (!transport->ws) {
+		/*
+		 * It's possible the transport was told to disconnect while waiting for input.
+		 * If so then the websocket will be NULL, so we don't want to continue.
+		 */
+		return 0;
+	}
+
+	do {
+		if (ast_websocket_read(transport->ws, &payload, &bytes_read, &opcode,
+				&fragmented) != 0) {
+			log_error(self, "read failure (%d): %s", opcode, strerror(errno));
+			return -1;
+		}
+
+		if (!bytes_read) {
+			continue;
+		}
+
+		if (total_bytes_read + bytes_read > size) {
+			log_error(self, "attempted to read too many bytes into (%jd) sized buffer", size);
+			return -1;
+		}
+
+		memcpy(buf + total_bytes_read, payload, bytes_read);
+		total_bytes_read += bytes_read;
+
+	} while (opcode == AST_WEBSOCKET_OPCODE_CONTINUATION);
+
+	switch (opcode) {
+	case AST_WEBSOCKET_OPCODE_CLOSE:
+		log_error(self, "closed");
+		return -1;
+	case AST_WEBSOCKET_OPCODE_BINARY:
+		*rtype = AST_AEAP_DATA_TYPE_BINARY;
+		break;
+	case AST_WEBSOCKET_OPCODE_TEXT:
+		*rtype = AST_AEAP_DATA_TYPE_STRING;
+
+		/* Append terminator, but check for overflow first */
+		if (total_bytes_read == size) {
+			log_error(self, "unable to write string terminator");
+			return -1;
+		}
+
+		*((char *)(buf + total_bytes_read)) = '\0';
+		break;
+	default:
+		/* Ignore all other message types */
+		return 0;
+	}
+
+	return total_bytes_read;
+}
+
+static intmax_t websocket_write(struct aeap_transport *self, const void *buf, intmax_t size,
+	enum AST_AEAP_DATA_TYPE wtype)
+{
+	struct aeap_transport_websocket *transport = (struct aeap_transport_websocket *)self;
+	intmax_t res = 0;
+
+	switch (wtype) {
+	case AST_AEAP_DATA_TYPE_BINARY:
+		res = ast_websocket_write(transport->ws, AST_WEBSOCKET_OPCODE_BINARY,
+			(char *)buf, size);
+		break;
+	case AST_AEAP_DATA_TYPE_STRING:
+		res = ast_websocket_write(transport->ws, AST_WEBSOCKET_OPCODE_TEXT,
+			(char *)buf, size);
+		break;
+	default:
+		break;
+	}
+
+	if (res < 0) {
+		log_error(self, "problem writing to websocket (closed)");
+
+		/*
+		 * If the underlying socket is closed then ensure the
+		 * transport is in a disconnected state as well.
+		 */
+		aeap_transport_disconnect(self);
+
+		return res;
+	}
+
+	return size;
+}
+
+static struct aeap_transport_vtable *transport_websocket_vtable(void)
+{
+	static struct aeap_transport_vtable websocket_vtable = {
+		.connect = websocket_connect,
+		.disconnect = websocket_disconnect,
+		.destroy = websocket_destroy,
+		.read = websocket_read,
+		.write = websocket_write,
+	};
+
+	return &websocket_vtable;
+}
+
+/*!
+ * \brief Initialize a transport websocket object, and set its virtual table
+ *
+ * \param transport The transport to initialize
+ *
+ * \returns 0 on success, -1 on error
+ */
+static int transport_websocket_init(struct aeap_transport_websocket *transport)
+{
+	transport->ws = NULL;
+
+	((struct aeap_transport *)transport)->vtable = transport_websocket_vtable();
+
+	return 0;
+}
+
+struct aeap_transport_websocket *aeap_transport_websocket_create(void)
+{
+	struct aeap_transport_websocket *transport;
+
+	transport = ast_calloc(1, sizeof(*transport));
+	if (!transport) {
+		ast_log(LOG_ERROR, "AEAP websocket: unable to create transport websocket");
+		return NULL;
+	}
+
+	if (transport_websocket_init(transport)) {
+		ast_free(transport);
+		return NULL;
+	}
+
+	return transport;
+}
diff --git a/res/res_aeap/transport_websocket.h b/res/res_aeap/transport_websocket.h
new file mode 100644
index 0000000000000000000000000000000000000000..d72657e22e364aa167ef8af00f14a9bd53ad337e
--- /dev/null
+++ b/res/res_aeap/transport_websocket.h
@@ -0,0 +1,34 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef RES_AEAP_TRANSPORT_WEBSOCKET_H
+#define RES_AEAP_TRANSPORT_WEBSOCKET_H
+
+/*!
+ * \brief Asterisk external application protocol websocket transport
+ */
+struct aeap_transport_websocket;
+
+/*!
+ * \brief Creates (heap allocated), and initializes a transport websocket
+ *
+ * \returns A transport websocket object, or NULL on error
+ */
+struct aeap_transport_websocket *aeap_transport_websocket_create(void);
+
+#endif /* RES_AEAP_TRANSPORT_WEBSOCKET_H */
diff --git a/res/res_agi.c b/res/res_agi.c
index 8bdb7ed3858e819281f72ea13964264deb9ae483..6debae1404ea4db320f8834bdf1a98397d6628c1 100644
--- a/res/res_agi.c
+++ b/res/res_agi.c
@@ -296,6 +296,9 @@
 			<replaceable>options</replaceable>.</para>
 			<para>Returns whatever the <replaceable>application</replaceable> returns, or
 			<literal>-2</literal> on failure to find <replaceable>application</replaceable>.</para>
+			<note>
+				<para>exec does not evaluate dialplan functions and variables unless it is explicitly enabled by setting the <variable>AGIEXECFULL</variable> variable to <literal>yes</literal>.</para>
+			</note>
 		</description>
 		<see-also>
 			<ref type="application">AGI</ref>
@@ -794,14 +797,10 @@
 			Enable/Disable Music on hold generator
 		</synopsis>
 		<syntax>
-			<parameter required="true">
+			<parameter name="boolean" required="true">
 				<enumlist>
-					<enum>
-						<parameter name="on" literal="true" required="true" />
-					</enum>
-					<enum>
-						<parameter name="off" literal="true" required="true" />
-					</enum>
+					<enum name="on" />
+					<enum name="off" />
 				</enumlist>
 			</parameter>
 			<parameter name="class" required="true" />
@@ -2908,6 +2907,7 @@ static int handle_recordfile(struct ast_channel *chan, AGI *agi, int argc, const
 	int gotsilence = 0;             /* did we timeout for silence? */
 	char *silencestr = NULL;
 	RAII_VAR(struct ast_format *, rfmt, NULL, ao2_cleanup);
+	struct ast_silence_generator *silgen = NULL;
 
 	/* XXX EAGI FIXME XXX */
 
@@ -2955,12 +2955,18 @@ static int handle_recordfile(struct ast_channel *chan, AGI *agi, int argc, const
 
 	/* backward compatibility, if no offset given, arg[6] would have been
 	 * caught below and taken to be a beep, else if it is a digit then it is a
-	 * offset */
-	if ((argc >6) && (sscanf(argv[6], "%30ld", &sample_offset) != 1) && (!strchr(argv[6], '=')))
-		res = ast_streamfile(chan, "beep", ast_channel_language(chan));
-
-	if ((argc > 7) && (!strchr(argv[7], '=')))
+	 * offset.
+	 *
+	 * In other words, if the argument does not look like the offset_samples
+	 * argument (a number) and it doesn't look like the silence argument (starts
+	 * with "s=") then it must be the beep argument. The beep argument has no
+	 * required value, the presence of anything in the argument slot we are
+	 * inspecting is an indication that the user wants a beep played.
+	 */
+	if ((argc > 6 && sscanf(argv[6], "%30ld", &sample_offset) != 1 && !ast_begins_with(argv[6], "s="))
+	   || (argc > 7 && !ast_begins_with(argv[7], "s="))) {
 		res = ast_streamfile(chan, "beep", ast_channel_language(chan));
+	}
 
 	if (!res)
 		res = ast_waitstream(chan, argv[4]);
@@ -2985,6 +2991,10 @@ static int handle_recordfile(struct ast_channel *chan, AGI *agi, int argc, const
 		ast_seekstream(fs, sample_offset, SEEK_SET);
 		ast_truncstream(fs);
 
+		if (ast_opt_transmit_silence) {
+			silgen = ast_channel_start_silence_generator(chan);
+		}
+
 		start = ast_tvnow();
 		while ((ms < 0) || ast_tvdiff_ms(ast_tvnow(), start) < ms) {
 			res = ast_waitfor(chan, ms - ast_tvdiff_ms(ast_tvnow(), start));
@@ -2993,6 +3003,8 @@ static int handle_recordfile(struct ast_channel *chan, AGI *agi, int argc, const
 				ast_agi_send(agi->fd, chan, "200 result=%d (waitfor) endpos=%ld\n", res,sample_offset);
 				if (sildet)
 					ast_dsp_free(sildet);
+				if (silgen)
+					ast_channel_stop_silence_generator(chan, silgen);
 				return RESULT_FAILURE;
 			}
 			f = ast_read(chan);
@@ -3001,6 +3013,8 @@ static int handle_recordfile(struct ast_channel *chan, AGI *agi, int argc, const
 				ast_agi_send(agi->fd, chan, "200 result=%d (hangup) endpos=%ld\n", -1, sample_offset);
 				if (sildet)
 					ast_dsp_free(sildet);
+				if (silgen)
+					ast_channel_stop_silence_generator(chan, silgen);
 				return RESULT_FAILURE;
 			}
 			switch(f->frametype) {
@@ -3017,6 +3031,8 @@ static int handle_recordfile(struct ast_channel *chan, AGI *agi, int argc, const
 					ast_frfree(f);
 					if (sildet)
 						ast_dsp_free(sildet);
+					if (silgen)
+						ast_channel_stop_silence_generator(chan, silgen);
 					return RESULT_SUCCESS;
 				}
 				break;
@@ -3068,6 +3084,10 @@ static int handle_recordfile(struct ast_channel *chan, AGI *agi, int argc, const
 		ast_dsp_free(sildet);
 	}
 
+	if (silgen) {
+		ast_channel_stop_silence_generator(chan, silgen);
+	}
+
 	return RESULT_SUCCESS;
 }
 
@@ -3125,6 +3145,9 @@ static int handle_exec(struct ast_channel *chan, AGI *agi, int argc, const char
 {
 	int res, workaround;
 	struct ast_app *app_to_exec;
+	const char *agi_exec_full_str;
+	int agi_exec_full;
+	struct ast_str *data_with_var = NULL;
 
 	if (argc < 2)
 		return RESULT_SHOWUSAGE;
@@ -3136,8 +3159,21 @@ static int handle_exec(struct ast_channel *chan, AGI *agi, int argc, const char
 		if (!(workaround = ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_WORKAROUNDS))) {
 			ast_set_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_WORKAROUNDS);
 		}
+		agi_exec_full_str = pbx_builtin_getvar_helper(chan, "AGIEXECFULL");
+		agi_exec_full = ast_true(agi_exec_full_str);
 		ast_channel_unlock(chan);
-		res = pbx_exec(chan, app_to_exec, argc == 2 ? "" : argv[2]);
+
+		if (agi_exec_full) {
+			if ((data_with_var = ast_str_create(16))) {
+				ast_str_substitute_variables(&data_with_var, 0, chan, argv[2]);
+				res = pbx_exec(chan, app_to_exec, argc == 2 ? "" : ast_str_buffer(data_with_var));
+				ast_free(data_with_var);
+			} else {
+				res = -2;
+			}
+		} else {
+			res = pbx_exec(chan, app_to_exec, argc == 2 ? "" : argv[2]);
+		}
 		if (!workaround) {
 			ast_channel_clear_flag(chan, AST_FLAG_DISABLE_WORKAROUNDS);
 		}
diff --git a/res/res_calendar.c b/res/res_calendar.c
index ba0079679ff908a377b6d30c6a6ee8e389ba56c5..bb1d63efe1b328da4e14b61b40bb48629f711b85 100644
--- a/res/res_calendar.c
+++ b/res/res_calendar.c
@@ -193,7 +193,9 @@
 			</parameter>
 		</syntax>
 		<description>
-			<para>Example: CALENDAR_WRITE(calendar,field1,field2,field3)=val1,val2,val3</para>
+			<example title="Set calendar fields">
+			same => n,Set(CALENDAR_WRITE(calendar,field1,field2,field3)=val1,val2,val3)
+			</example>
 			<para>The field and value arguments can easily be set/passed using the HASHKEYS() and HASH() functions</para>
 			<variablelist>
 				<variable name="CALENDAR_SUCCESS">
@@ -998,10 +1000,15 @@ static int schedule_calendar_event(struct ast_calendar *cal, struct ast_calendar
 	if (!cmp_event || old_event->end != event->end) {
 		changed = 1;
 		devstate_sched_end = (event->end - now.tv_sec) * 1000;
-		ast_mutex_lock(&refreshlock);
-		AST_SCHED_REPLACE(old_event->bs_end_sched, sched, devstate_sched_end, calendar_devstate_change, old_event);
-		ast_mutex_unlock(&refreshlock);
-		ast_debug(3, "Calendar bs_end event notification scheduled to happen in %ld ms\n", (long) devstate_sched_end);
+
+		if (devstate_sched_end <= 0) { /* if we let this slip by, Asterisk will assert */
+			ast_log(LOG_WARNING, "Whoops! Event end notification scheduled in the past: %ld ms\n", (long) devstate_sched_end);
+		} else {
+			ast_mutex_lock(&refreshlock);
+			AST_SCHED_REPLACE(old_event->bs_end_sched, sched, devstate_sched_end, calendar_devstate_change, old_event);
+			ast_mutex_unlock(&refreshlock);
+			ast_debug(3, "Calendar bs_end event notification scheduled to happen in %ld ms\n", (long) devstate_sched_end);
+		}
 	}
 
 	if (changed) {
@@ -1601,6 +1608,21 @@ static char *epoch_to_string(char *buf, size_t buflen, time_t epoch)
 	return buf;
 }
 
+static const char *ast_calendar_busy_state_to_str(enum ast_calendar_busy_state busy_state)
+{
+	switch (busy_state) {
+	case AST_CALENDAR_BS_FREE:
+		return "Free";
+	case AST_CALENDAR_BS_BUSY_TENTATIVE:
+		return "Busy (Tentative)";
+	case AST_CALENDAR_BS_BUSY:
+		return "Busy";
+	default:
+		return "Unknown (Busy)";
+	}
+}
+
+
 static char *handle_show_calendar(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
 #define FORMAT  "%-18.18s : %-20.20s\n"
@@ -1676,6 +1698,7 @@ static char *handle_show_calendar(struct ast_cli_entry *e, int cmd, struct ast_c
 		ast_cli(a->fd, FORMAT2, "Start", epoch_to_string(buf, sizeof(buf), event->start));
 		ast_cli(a->fd, FORMAT2, "End", epoch_to_string(buf, sizeof(buf), event->end));
 		ast_cli(a->fd, FORMAT2, "Alarm", epoch_to_string(buf, sizeof(buf), event->alarm));
+		ast_cli(a->fd, FORMAT2, "Busy State", ast_calendar_busy_state_to_str(event->busy_state));
 		ast_cli(a->fd, "\n");
 
 		event = ast_calendar_unref_event(event);
diff --git a/res/res_calendar_caldav.c b/res/res_calendar_caldav.c
index 9bdde0e94bb5a357f40842be7d942558cbd1fae6..a5266f3369bf2c2b2bf6afa15df4108c89487233 100644
--- a/res/res_calendar_caldav.c
+++ b/res/res_calendar_caldav.c
@@ -404,8 +404,8 @@ static void caldav_add_event(icalcomponent *comp, struct icaltime_span *span, vo
 		if (!ast_strlen_zero(event->summary)) {
 			ast_string_field_set(event, uid, event->summary);
 		} else {
-			char tmp[100];
-			snprintf(tmp, sizeof(tmp), "%ld", event->start);
+			char tmp[AST_TIME_T_LEN];
+			ast_time_t_to_string(event->start, tmp, sizeof(tmp));
 			ast_string_field_set(event, uid, tmp);
 		}
 	}
diff --git a/res/res_calendar_icalendar.c b/res/res_calendar_icalendar.c
index 999cf0ecf4ae68c7df5a3e75aa9303afd74beb5b..29be671d359b40285bf1bdeccc94b6e0b0e8016d 100644
--- a/res/res_calendar_icalendar.c
+++ b/res/res_calendar_icalendar.c
@@ -245,8 +245,8 @@ static void icalendar_add_event(icalcomponent *comp, struct icaltime_span *span,
 		if (!ast_strlen_zero(event->summary)) {
 			ast_string_field_set(event, uid, event->summary);
 		} else {
-			char tmp[100];
-			snprintf(tmp, sizeof(tmp), "%ld", event->start);
+			char tmp[AST_TIME_T_LEN];
+			ast_time_t_to_string(event->start, tmp, sizeof(tmp));
 			ast_string_field_set(event, uid, tmp);
 		}
 	}
@@ -465,6 +465,7 @@ static void *ical_load_calendar(void *void_data)
 	pvt->session = ne_session_create(pvt->uri.scheme, pvt->uri.host, pvt->uri.port);
 	ne_redirect_register(pvt->session);
 	ne_set_server_auth(pvt->session, auth_credentials, pvt);
+	ne_set_useragent(pvt->session, "Asterisk");
 	if (!strcasecmp(pvt->uri.scheme, "https")) {
 		ne_ssl_trust_default_ca(pvt->session);
 	}
diff --git a/res/res_cliexec.c b/res/res_cliexec.c
new file mode 100644
index 0000000000000000000000000000000000000000..b1e13f94b36769050aded98d9b4d2cbc491fe4cc
--- /dev/null
+++ b/res/res_cliexec.c
@@ -0,0 +1,160 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Naveen Albert
+ *
+ * Naveen Albert <asterisk@phreaknet.org>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \author Naveen Albert <asterisk@phreaknet.org>
+ *
+ * \brief Execute dialplan applications from the CLI
+ *
+ */
+
+/*** MODULEINFO
+	<defaultenabled>no</defaultenabled>
+	<support_level>extended</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/utils.h"
+#include "asterisk/frame.h"
+#include "asterisk/format_cache.h"
+
+static const struct ast_channel_tech mock_channel_tech = {
+};
+
+static int cli_chan = 0;
+
+/*! \brief CLI support for executing application */
+static char *handle_exec(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_channel *c = NULL;
+	RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
+	char *app_name, *app_args;
+	int ret = 0;
+	struct ast_app *app;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "dialplan exec application";
+		e->usage =
+			"Usage: dialplan exec application <appname> [<args>]\n"
+			"       Execute a single dialplan application call for\n"
+			"       testing. A mock channel is used to execute\n"
+			"       the application, so it may not make\n"
+			"       sense to use all applications, and only\n"
+			"       global variables should be used.\n"
+			"       The ulaw, alaw, and h264 codecs are available.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != e->args + 1 && a->argc != e->args + 2) {
+		return CLI_SHOWUSAGE;
+	}
+
+	app_name = (char *) a->argv[3];
+	app_args = a->argc == e->args + 2 ? (char *) a->argv[4] : NULL;
+
+	if (!app_name) {
+		return CLI_FAILURE;
+	}
+
+	caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+	if (!caps) {
+		ast_log(LOG_WARNING, "Could not allocate an empty format capabilities structure\n");
+		return CLI_FAILURE;
+	}
+
+	if (ast_format_cap_append(caps, ast_format_ulaw, 0)) {
+		ast_log(LOG_WARNING, "Failed to append a ulaw format to capabilities for channel nativeformats\n");
+		return CLI_FAILURE;
+	}
+
+	if (ast_format_cap_append(caps, ast_format_alaw, 0)) {
+		ast_log(LOG_WARNING, "Failed to append an alaw format to capabilities for channel nativeformats\n");
+		return CLI_FAILURE;
+	}
+
+	if (ast_format_cap_append(caps, ast_format_h264, 0)) {
+		ast_log(LOG_WARNING, "Failed to append an h264 format to capabilities for channel nativeformats\n");
+		return CLI_FAILURE;
+	}
+
+	c = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, "CLIExec/%d", ++cli_chan);
+	if (!c) {
+		ast_cli(a->fd, "Unable to allocate mock channel for application execution.\n");
+		return CLI_FAILURE;
+	}
+	ast_channel_tech_set(c, &mock_channel_tech);
+	ast_channel_nativeformats_set(c, caps);
+	ast_channel_set_writeformat(c, ast_format_slin);
+	ast_channel_set_rawwriteformat(c, ast_format_slin);
+	ast_channel_set_readformat(c, ast_format_slin);
+	ast_channel_set_rawreadformat(c, ast_format_slin);
+	ast_channel_unlock(c);
+
+	app = pbx_findapp(app_name);
+	if (!app) {
+		ast_log(LOG_WARNING, "Could not find application (%s)\n", app_name);
+		ast_hangup(c);
+		return CLI_FAILURE;
+	} else {
+		struct ast_str *substituted_args = ast_str_create(16);
+
+		if (substituted_args) {
+			ast_str_substitute_variables(&substituted_args, 0, c, app_args);
+			ast_cli(a->fd, "Executing: %s(%s)\n", app_name, ast_str_buffer(substituted_args));
+			ret = pbx_exec(c, app, ast_str_buffer(substituted_args));
+			ast_free(substituted_args);
+		} else {
+			ast_log(LOG_WARNING, "Could not substitute application argument variables for %s\n", app_name);
+			ast_cli(a->fd, "Executing: %s(%s)\n", app_name, app_args);
+			ret = pbx_exec(c, app, app_args);
+		}
+	}
+
+	ast_hangup(c); /* no need to unref separately */
+
+	ast_cli(a->fd, "Return Value: %s (%d)\n", ret ? "Failure" : "Success", ret);
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_cliorig[] = {
+	AST_CLI_DEFINE(handle_exec, "Execute a dialplan application"),
+};
+
+static int unload_module(void)
+{
+	return ast_cli_unregister_multiple(cli_cliorig, ARRAY_LEN(cli_cliorig));
+}
+
+static int load_module(void)
+{
+	int res;
+	res = ast_cli_register_multiple(cli_cliorig, ARRAY_LEN(cli_cliorig));
+	return res ? AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Simple dialplan execution from the CLI");
diff --git a/res/res_config_odbc.c b/res/res_config_odbc.c
index 178a4837a9cba5784505488dbaa04d197ed83d2f..b6a878a8537148b4d53d101620c1016fe1735d11 100644
--- a/res/res_config_odbc.c
+++ b/res/res_config_odbc.c
@@ -1133,6 +1133,7 @@ static int require_odbc(const char *database, const char *table, va_list ap)
 					break;
 				case SQL_TYPE_TIMESTAMP:
 				case SQL_TIMESTAMP:
+				case SQL_DATETIME:
 					if (type != RQ_DATE && type != RQ_DATETIME) {
 						warn_type(col, type);
 					}
diff --git a/res/res_config_pgsql.c b/res/res_config_pgsql.c
index dbcd9e77b6507cb06d5c6becae466a7934e9b48e..62a3565e024ef4902b66833044678aa5e17ec390 100644
--- a/res/res_config_pgsql.c
+++ b/res/res_config_pgsql.c
@@ -1287,7 +1287,7 @@ static int require_pgsql(const char *database, const char *tablename, va_list ap
 				res = -1;
 			} else {
 				struct ast_str *sql = ast_str_create(100);
-				char fieldtype[10];
+				char fieldtype[20];
 				PGresult *result;
 
 				if (requirements == RQ_CREATECHAR || type == RQ_CHAR) {
diff --git a/res/res_config_sqlite.c b/res/res_config_sqlite.c
deleted file mode 100644
index 232600018c4e7f2ee355a97b7fbc49d382066038..0000000000000000000000000000000000000000
--- a/res/res_config_sqlite.c
+++ /dev/null
@@ -1,1789 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2006, Proformatique
- *
- * Written by Richard Braun <rbraun@proformatique.com>
- *
- * Based on res_sqlite3 by Anthony Minessale II,
- * and res_config_mysql by Matthew Boehm
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*!
- * \page res_config_sqlite
- *
- * \section intro_sec Presentation
- *
- * res_config_sqlite is a module for the Asterisk Open Source PBX to
- * support SQLite 2 databases. It can be used to fetch configuration
- * from a database (static configuration files and/or using the Asterisk
- * RealTime Architecture - ARA).  It can also be used to log CDR entries.
- * Note that Asterisk already comes with a module named cdr_sqlite.
- * There are two reasons for including it in res_config_sqlite:
- * the first is that rewriting it was a training to learn how to write a
- * simple module for Asterisk, the other is to have the same database open for
- * all kinds of operations, which improves reliability and performance.
- *
- * \section conf_sec Configuration
- *
- * The main configuration file is res_config_sqlite.conf.sample It must be readable or
- * res_config_sqlite will fail to start. It is suggested to use the sample file
- * in this package as a starting point. The file has only one section
- * named <code>general</code>. Here are the supported parameters :
- *
- * <dl>
- *	<dt><code>dbfile</code></dt>
- *	<dd>The absolute path to the SQLite database (the file can be non existent,
- *			res_config_sqlite will create it if it has the appropriate rights)</dd>
- *	<dt><code>config_table</code></dt>
- *	<dd>The table used for static configuration</dd>
- *	<dt><code>cdr_table</code></dt>
- *	<dd>The table used to store CDR entries (if ommitted, CDR support is
- *			disabled)</dd>
- * </dl>
- *
- * To use res_config_sqlite for static and/or RealTime configuration, refer to the
- * Asterisk documentation. The file tables.sql can be used to create the
- * needed tables.
- *
- * \section status_sec Driver status
- *
- * The CLI command <code>show sqlite status</code> returns status information
- * about the running driver.
- *
- * \section credits_sec Credits
- *
- * res_config_sqlite was developed by Richard Braun at the Proformatique company.
- */
-
-/*!
- * \file
- * \brief res_config_sqlite module.
- */
-
-/*! \li \ref res_config_sqlite.c uses the configuration file \ref res_config_sqlite.conf
- * \addtogroup configuration_file Configuration Files
- */
-
-/*!
- * \page res_config_sqlite.conf res_config_sqlite.conf
- * \verbinclude res_config_sqlite.conf.sample
- */
-
-/*** MODULEINFO
-	<depend>sqlite</depend>
-	<support_level>deprecated</support_level>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include "asterisk.h"
-
-#include <sqlite.h>
-
-#include "asterisk/logger.h"
-#include "asterisk/app.h"
-#include "asterisk/pbx.h"
-#include "asterisk/cdr.h"
-#include "asterisk/cli.h"
-#include "asterisk/lock.h"
-#include "asterisk/config.h"
-#include "asterisk/module.h"
-#include "asterisk/linkedlists.h"
-
-#define MACRO_BEGIN	do {
-#define MACRO_END	} while (0)
-
-#define RES_CONFIG_SQLITE_NAME "res_config_sqlite"
-#define RES_CONFIG_SQLITE_DRIVER "sqlite"
-#define RES_CONFIG_SQLITE_DESCRIPTION "Resource Module for SQLite 2"
-#define RES_CONFIG_SQLITE_CONF_FILE "res_config_sqlite.conf"
-
-enum {
-	RES_CONFIG_SQLITE_CONFIG_ID,
-	RES_CONFIG_SQLITE_CONFIG_CAT_METRIC,
-	RES_CONFIG_SQLITE_CONFIG_VAR_METRIC,
-	RES_CONFIG_SQLITE_CONFIG_COMMENTED,
-	RES_CONFIG_SQLITE_CONFIG_FILENAME,
-	RES_CONFIG_SQLITE_CONFIG_CATEGORY,
-	RES_CONFIG_SQLITE_CONFIG_VAR_NAME,
-	RES_CONFIG_SQLITE_CONFIG_VAR_VAL,
-	RES_CONFIG_SQLITE_CONFIG_COLUMNS,
-};
-
-#define SET_VAR(config, to, from)			\
-MACRO_BEGIN						\
-	int __error;					\
-							\
-	__error = set_var(&to, #to, from->value);	\
-							\
-	if (__error) {					\
-		ast_config_destroy(config);		\
-		unload_config();			\
-		return 1;				\
-	}						\
-MACRO_END
-
-AST_THREADSTORAGE(sql_buf);
-AST_THREADSTORAGE(where_buf);
-
-/*!
- * Maximum number of loops before giving up executing a query. Calls to
- * sqlite_xxx() functions which can return SQLITE_BUSY
- * are enclosed by RES_CONFIG_SQLITE_BEGIN and RES_CONFIG_SQLITE_END, e.g.
- * <pre>
- * char *errormsg;
- * int error;
- *
- * RES_CONFIG_SQLITE_BEGIN
- *	 error = sqlite_exec(db, query, NULL, NULL, &errormsg);
- * RES_CONFIG_SQLITE_END(error)
- *
- * if (error)
- *	 ...;
- * </pre>
- */
-#define RES_CONFIG_SQLITE_MAX_LOOPS 10
-
-/*!
- * Macro used before executing a query.
- *
- * \see RES_CONFIG_SQLITE_MAX_LOOPS.
- */
-#define RES_CONFIG_SQLITE_BEGIN						\
-MACRO_BEGIN								\
-	int __i;							\
-									\
-	for (__i = 0; __i < RES_CONFIG_SQLITE_MAX_LOOPS; __i++)	{
-
-/*!
- * Macro used after executing a query.
- *
- * \see RES_CONFIG_SQLITE_MAX_LOOPS.
- */
-#define RES_CONFIG_SQLITE_END(error)					\
-		if (error != SQLITE_BUSY)	\
-			break;						\
-		usleep(1000);						\
-	}								\
-MACRO_END;
-
-/*!
- * Structure sent to the SQLite callback function for static configuration.
- *
- * \see add_cfg_entry()
- */
-struct cfg_entry_args {
-	struct ast_config *cfg;
-	struct ast_category *cat;
-	char *cat_name;
-	struct ast_flags flags;
-	const char *who_asked;
-};
-
-/*!
- * Structure sent to the SQLite callback function for RealTime configuration.
- *
- * \see add_rt_cfg_entry()
- */
-struct rt_cfg_entry_args {
-	struct ast_variable *var;
-	struct ast_variable *last;
-};
-
-/*!
- * Structure sent to the SQLite callback function for RealTime configuration
- * (realtime_multi_handler()).
- *
- * \see add_rt_multi_cfg_entry()
- */
-struct rt_multi_cfg_entry_args {
-	struct ast_config *cfg;
-	char *initfield;
-};
-
-/*!
- * \brief Allocate a variable.
- * \param var the address of the variable to set (it will be allocated)
- * \param name the name of the variable (for error handling)
- * \param value the value to store in var
- * \retval 0 on success
- * \retval 1 if an allocation error occurred
- */
-static int set_var(char **var, const char *name, const char *value);
-
-/*!
- * \brief Load the configuration file.
- * \see unload_config()
- *
- * This function sets dbfile, config_table, and cdr_table. It calls
- * check_vars() before returning, and unload_config() if an error occurred.
- *
- * \retval 0 on success
- * \retval 1 if an error occurred
- */
-static int load_config(void);
-
-/*!
- * \brief Free resources related to configuration.
- * \see load_config()
- */
-static void unload_config(void);
-
-/*!
- * \brief Asterisk callback function for CDR support.
- * \param cdr the CDR entry Asterisk sends us.
- *
- * Asterisk will call this function each time a CDR entry must be logged if
- * CDR support is enabled.
- *
- * \retval 0 on success
- * \retval 1 if an error occurred
- */
-static int cdr_handler(struct ast_cdr *cdr);
-
-/*!
- * \brief SQLite callback function for static configuration.
- *
- * This function is passed to the SQLite engine as a callback function to
- * parse a row and store it in a struct ast_config object. It relies on
- * resulting rows being sorted by category.
- *
- * \param arg a pointer to a struct cfg_entry_args object
- * \param argc number of columns
- * \param argv values in the row
- * \param columnNames names and types of the columns
- * \retval 0 on success
- * \retval 1 if an error occurred
- * \see cfg_entry_args
- * \see sql_get_config_table
- * \see config_handler()
- */
-static int add_cfg_entry(void *arg, int argc, char **argv, char **columnNames);
-
-/*!
- * \brief Asterisk callback function for static configuration.
- *
- * Asterisk will call this function when it loads its static configuration,
- * which usually happens at startup and reload.
- *
- * \param database the database to use (ignored)
- * \param table the table to use
- * \param file the file to load from the database
- * \param cfg the struct ast_config object to use when storing variables
- * \param flags Optional flags.  Not used.
- * \param suggested_incl suggest include.
- * \param who_asked
- * \retval cfg object
- * \retval NULL if an error occurred
- * \see add_cfg_entry()
- */
-static struct ast_config * config_handler(const char *database, const char *table, const char *file,
-	struct ast_config *cfg, struct ast_flags flags, const char *suggested_incl, const char *who_asked);
-
-/*!
- * \brief SQLite callback function for RealTime configuration.
- *
- * This function is passed to the SQLite engine as a callback function to
- * parse a row and store it in a linked list of struct ast_variable objects.
- *
- * \param arg a pointer to a struct rt_cfg_entry_args object
- * \param argc number of columns
- * \param argv values in the row
- * \param columnNames names and types of the columns
- * \retval 0 on success.
- * \retval 1 if an error occurred.
- * \see rt_cfg_entry_args
- * \see realtime_handler()
- */
-static int add_rt_cfg_entry(void *arg, int argc, char **argv,
-	char **columnNames);
-
-/*!
- * \brief Asterisk callback function for RealTime configuration.
- *
- * Asterisk will call this function each time it requires a variable
- * through the RealTime architecture. ap is a list of parameters and
- * values used to find a specific row, e.g one parameter "name" and
- * one value "123" so that the SQL query becomes <code>SELECT * FROM
- * table WHERE name = '123';</code>.
- *
- * \param database the database to use (ignored)
- * \param table the table to use
- * \param fields list of parameters and values to match
- *
- * \retval a linked list of struct ast_variable objects
- * \retval NULL if an error occurred
- * \see add_rt_cfg_entry()
- */
-static struct ast_variable * realtime_handler(const char *database,
-	const char *table, const struct ast_variable *fields);
-
-/*!
- * \brief SQLite callback function for RealTime configuration.
- *
- * This function performs the same actions as add_rt_cfg_entry() except
- * that the rt_multi_cfg_entry_args structure is designed to store
- * categories in addition to variables.
- *
- * \param arg a pointer to a struct rt_multi_cfg_entry_args object
- * \param argc number of columns
- * \param argv values in the row
- * \param columnNames names and types of the columns
- * \retval 0 on success.
- * \retval 1 if an error occurred.
- * \see rt_multi_cfg_entry_args
- * \see realtime_multi_handler()
- */
-static int add_rt_multi_cfg_entry(void *arg, int argc, char **argv,
-	char **columnNames);
-
-/*!
- * \brief Asterisk callback function for RealTime configuration.
- *
- * This function performs the same actions as realtime_handler() except
- * that it can store variables per category, and can return several
- * categories.
- *
- * \param database the database to use (ignored)
- * \param table the table to use
- * \param fields list of parameters and values to match
- * \retval a struct ast_config object storing categories and variables.
- * \retval NULL if an error occurred.
- *
- * \see add_rt_multi_cfg_entry()
- */
-static struct ast_config * realtime_multi_handler(const char *database,
-	const char *table, const struct ast_variable *fields);
-
-/*!
- * \brief Asterisk callback function for RealTime configuration (variable
- * update).
- *
- * Asterisk will call this function each time a variable has been modified
- * internally and must be updated in the backend engine. keyfield and entity
- * are used to find the row to update, e.g. <code>UPDATE table SET ... WHERE
- * keyfield = 'entity';</code>. ap is a list of parameters and values with the
- * same format as the other realtime functions.
- *
- * \param database the database to use (ignored)
- * \param table the table to use
- * \param keyfield the column of the matching cell
- * \param entity the value of the matching cell
- * \param fields list of parameters and new values to update in the database
- * \retval the number of affected rows.
- * \retval -1 if an error occurred.
- */
-static int realtime_update_handler(const char *database, const char *table,
-	const char *keyfield, const char *entity, const struct ast_variable *fields);
-static int realtime_update2_handler(const char *database, const char *table,
-	const struct ast_variable *lookup_fields, const struct ast_variable *update_fields);
-
-/*!
- * \brief Asterisk callback function for RealTime configuration (variable
- * create/store).
- *
- * Asterisk will call this function each time a variable has been created
- * internally and must be stored in the backend engine.
- * are used to find the row to update, e.g. ap is a list of parameters and
- * values with the same format as the other realtime functions.
- *
- * \param database the database to use (ignored)
- * \param table the table to use
- * \param fields list of parameters and new values to insert into the database
- * \retval the rowid of inserted row.
- * \retval -1 if an error occurred.
- */
-static int realtime_store_handler(const char *database, const char *table,
-	const struct ast_variable *fields);
-
-/*!
- * \brief Asterisk callback function for RealTime configuration (destroys
- * variable).
- *
- * Asterisk will call this function each time a variable has been destroyed
- * internally and must be removed from the backend engine. keyfield and entity
- * are used to find the row to delete, e.g. <code>DELETE FROM table WHERE
- * keyfield = 'entity';</code>. ap is a list of parameters and values with the
- * same format as the other realtime functions.
- *
- * \param database the database to use (ignored)
- * \param table the table to use
- * \param keyfield the column of the matching cell
- * \param entity the value of the matching cell
- * \param fields list of additional parameters for cell matching
- * \retval the number of affected rows.
- * \retval -1 if an error occurred.
- */
-static int realtime_destroy_handler(const char *database, const char *table,
-	const char *keyfield, const char *entity, const struct ast_variable *fields);
-
-/*!
- * \brief Asterisk callback function for the CLI status command.
- *
- * \param e CLI command
- * \param cmd
- * \param a CLI argument list
- * \return RESULT_SUCCESS
- */
-static char *handle_cli_show_sqlite_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
-static char *handle_cli_sqlite_show_tables(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
-
-static int realtime_require_handler(const char *database, const char *table, va_list ap);
-static int realtime_unload_handler(const char *unused, const char *tablename);
-
-/*! The SQLite database object. */
-static sqlite *db;
-
-/*! Set to 1 if CDR support is enabled. */
-static int use_cdr;
-
-/*! Set to 1 if the CDR callback function was registered. */
-static int cdr_registered;
-
-/*! Set to 1 if the CLI status command callback function was registered. */
-static int cli_status_registered;
-
-/*! The path of the database file. */
-static char *dbfile;
-
-/*! The name of the static configuration table. */
-static char *config_table;
-
-/*! The name of the table used to store CDR entries. */
-static char *cdr_table;
-
-/*!
- * The structure specifying all callback functions used by Asterisk for static
- * and RealTime configuration.
- */
-static struct ast_config_engine sqlite_engine =
-{
-	.name = RES_CONFIG_SQLITE_DRIVER,
-	.load_func = config_handler,
-	.realtime_func = realtime_handler,
-	.realtime_multi_func = realtime_multi_handler,
-	.store_func = realtime_store_handler,
-	.destroy_func = realtime_destroy_handler,
-	.update_func = realtime_update_handler,
-	.update2_func = realtime_update2_handler,
-	.require_func = realtime_require_handler,
-	.unload_func = realtime_unload_handler,
-};
-
-/*!
- * The mutex used to prevent simultaneous access to the SQLite database.
- */
-AST_MUTEX_DEFINE_STATIC(mutex);
-
-/*!
- * Structure containing details and callback functions for the CLI status
- * command.
- */
-static struct ast_cli_entry cli_status[] = {
-	AST_CLI_DEFINE(handle_cli_show_sqlite_status, "Show status information about the SQLite 2 driver"),
-	AST_CLI_DEFINE(handle_cli_sqlite_show_tables, "Cached table information about the SQLite 2 driver"),
-};
-
-struct sqlite_cache_columns {
-	char *name;
-	char *type;
-	unsigned char isint;    /*!< By definition, only INTEGER PRIMARY KEY is an integer; everything else is a string. */
-	AST_RWLIST_ENTRY(sqlite_cache_columns) list;
-};
-
-struct sqlite_cache_tables {
-	char *name;
-	AST_RWLIST_HEAD(_columns, sqlite_cache_columns) columns;
-	AST_RWLIST_ENTRY(sqlite_cache_tables) list;
-};
-
-static AST_RWLIST_HEAD_STATIC(sqlite_tables, sqlite_cache_tables);
-
-/*
- * Taken from Asterisk 1.2 cdr_sqlite.so.
- */
-
-/*! SQL query format to create the CDR table if non existent. */
-static char *sql_create_cdr_table =
-"CREATE TABLE '%q' (\n"
-"	id		INTEGER,\n"
-"	clid		VARCHAR(80)	NOT NULL	DEFAULT '',\n"
-"	src		VARCHAR(80)	NOT NULL	DEFAULT '',\n"
-"	dst		VARCHAR(80)	NOT NULL	DEFAULT '',\n"
-"	dcontext	VARCHAR(80)	NOT NULL	DEFAULT '',\n"
-"	channel		VARCHAR(80)	NOT NULL	DEFAULT '',\n"
-"	dstchannel	VARCHAR(80)	NOT NULL	DEFAULT '',\n"
-"	lastapp		VARCHAR(80)	NOT NULL	DEFAULT '',\n"
-"	lastdata	VARCHAR(80)	NOT NULL	DEFAULT '',\n"
-"	start		DATETIME	NOT NULL	DEFAULT '0000-00-00 00:00:00',\n"
-"	answer		DATETIME	NOT NULL	DEFAULT '0000-00-00 00:00:00',\n"
-"	end		DATETIME	NOT NULL	DEFAULT '0000-00-00 00:00:00',\n"
-"	duration	INT(11)		NOT NULL	DEFAULT 0,\n"
-"	billsec		INT(11)		NOT NULL	DEFAULT 0,\n"
-"	disposition	VARCHAR(45)	NOT NULL	DEFAULT '',\n"
-"	amaflags	INT(11)		NOT NULL	DEFAULT 0,\n"
-"	accountcode	VARCHAR(20)	NOT NULL	DEFAULT '',\n"
-"	uniqueid	VARCHAR(32)	NOT NULL	DEFAULT '',\n"
-"	userfield	VARCHAR(255)	NOT NULL	DEFAULT '',\n"
-"	PRIMARY KEY	(id)\n"
-");";
-
-/*!
- * SQL query format to describe the table structure
- */
-#define sql_table_structure "SELECT sql FROM sqlite_master WHERE type='table' AND tbl_name='%s'"
-
-/*!
- * SQL query format to fetch the static configuration of a file.
- * Rows must be sorted by category.
- *
- * \see add_cfg_entry()
- */
-#define sql_get_config_table \
-	"SELECT *" \
-	"	FROM '%q'" \
-	"	WHERE filename = '%q' AND commented = 0" \
-	"	ORDER BY cat_metric ASC, var_metric ASC;"
-
-static void free_table(struct sqlite_cache_tables *tblptr)
-{
-	struct sqlite_cache_columns *col;
-
-	/* Obtain a write lock to ensure there are no read locks outstanding */
-	AST_RWLIST_WRLOCK(&(tblptr->columns));
-	while ((col = AST_RWLIST_REMOVE_HEAD(&(tblptr->columns), list))) {
-		ast_free(col);
-	}
-	AST_RWLIST_UNLOCK(&(tblptr->columns));
-	AST_RWLIST_HEAD_DESTROY(&(tblptr->columns));
-	ast_free(tblptr);
-}
-
-static int find_table_cb(void *vtblptr, int argc, char **argv, char **columnNames)
-{
-	struct sqlite_cache_tables *tblptr = vtblptr;
-	char *sql = ast_strdupa(argv[0]), *start, *end, *type, *remainder;
-	int i;
-	AST_DECLARE_APP_ARGS(fie,
-		AST_APP_ARG(ld)[100]; /* This means we support up to 100 columns per table */
-	);
-	struct sqlite_cache_columns *col;
-
-	/* This is really fun.  We get to parse an SQL statement to figure out
-	 * what columns are in the table.
-	 */
-	if ((start = strchr(sql, '(')) && (end = strrchr(sql, ')'))) {
-		start++;
-		*end = '\0';
-	} else {
-		/* Abort */
-		return -1;
-	}
-
-	AST_STANDARD_APP_ARGS(fie, start);
-	for (i = 0; i < fie.argc; i++) {
-		fie.ld[i] = ast_skip_blanks(fie.ld[i]);
-		ast_debug(5, "Found field: %s\n", fie.ld[i]);
-		if (strncasecmp(fie.ld[i], "PRIMARY KEY", 11) == 0 && (start = strchr(fie.ld[i], '(')) && (end = strchr(fie.ld[i], ')'))) {
-			*end = '\0';
-			AST_RWLIST_TRAVERSE(&(tblptr->columns), col, list) {
-				if (strcasecmp(start + 1, col->name) == 0 && strcasestr(col->type, "INTEGER")) {
-					col->isint = 1;
-				}
-			}
-			continue;
-		}
-		/* type delimiter could be any space character */
-		for (type = fie.ld[i]; *type > 32; type++);
-		*type++ = '\0';
-		type = ast_skip_blanks(type);
-		for (remainder = type; *remainder > 32; remainder++);
-		*remainder = '\0';
-		if (!(col = ast_calloc(1, sizeof(*col) + strlen(fie.ld[i]) + strlen(type) + 2))) {
-			return -1;
-		}
-		col->name = (char *)col + sizeof(*col);
-		col->type = (char *)col + sizeof(*col) + strlen(fie.ld[i]) + 1;
-		strcpy(col->name, fie.ld[i]); /* SAFE */
-		strcpy(col->type, type); /* SAFE */
-		if (strcasestr(col->type, "INTEGER") && strcasestr(col->type, "PRIMARY KEY")) {
-			col->isint = 1;
-		}
-		AST_LIST_INSERT_TAIL(&(tblptr->columns), col, list);
-	}
-	return 0;
-}
-
-static struct sqlite_cache_tables *find_table(const char *tablename)
-{
-	struct sqlite_cache_tables *tblptr;
-	int i, err;
-	char *sql, *errstr = NULL;
-
-	AST_RWLIST_RDLOCK(&sqlite_tables);
-
-	for (i = 0; i < 2; i++) {
-		AST_RWLIST_TRAVERSE(&sqlite_tables, tblptr, list) {
-			if (strcmp(tblptr->name, tablename) == 0) {
-				break;
-			}
-		}
-		if (tblptr) {
-			AST_RWLIST_RDLOCK(&(tblptr->columns));
-			AST_RWLIST_UNLOCK(&sqlite_tables);
-			return tblptr;
-		}
-
-		if (i == 0) {
-			AST_RWLIST_UNLOCK(&sqlite_tables);
-			AST_RWLIST_WRLOCK(&sqlite_tables);
-		}
-	}
-
-	/* Table structure not cached; build the structure now */
-	if (ast_asprintf(&sql, sql_table_structure, tablename) < 0) {
-		sql = NULL;
-	}
-	if (!(tblptr = ast_calloc(1, sizeof(*tblptr) + strlen(tablename) + 1))) {
-		AST_RWLIST_UNLOCK(&sqlite_tables);
-		ast_log(LOG_ERROR, "Memory error.  Cannot cache table '%s'\n", tablename);
-		ast_free(sql);
-		return NULL;
-	}
-	tblptr->name = (char *)tblptr + sizeof(*tblptr);
-	strcpy(tblptr->name, tablename); /* SAFE */
-	AST_RWLIST_HEAD_INIT(&(tblptr->columns));
-
-	ast_debug(1, "About to query table structure: %s\n", sql);
-
-	ast_mutex_lock(&mutex);
-	if ((err = sqlite_exec(db, sql, find_table_cb, tblptr, &errstr))) {
-		ast_mutex_unlock(&mutex);
-		ast_log(LOG_WARNING, "SQLite error %d: %s\n", err, errstr);
-		ast_free(errstr);
-		free_table(tblptr);
-		AST_RWLIST_UNLOCK(&sqlite_tables);
-		ast_free(sql);
-		return NULL;
-	}
-	ast_mutex_unlock(&mutex);
-	ast_free(sql);
-
-	if (AST_LIST_EMPTY(&(tblptr->columns))) {
-		free_table(tblptr);
-		AST_RWLIST_UNLOCK(&sqlite_tables);
-		return NULL;
-	}
-
-	AST_RWLIST_INSERT_TAIL(&sqlite_tables, tblptr, list);
-	AST_RWLIST_RDLOCK(&(tblptr->columns));
-	AST_RWLIST_UNLOCK(&sqlite_tables);
-	return tblptr;
-}
-
-#define release_table(a)	AST_RWLIST_UNLOCK(&((a)->columns))
-
-static int set_var(char **var, const char *name, const char *value)
-{
-	if (*var)
-		ast_free(*var);
-
-	*var = ast_strdup(value);
-
-	if (!*var) {
-		ast_log(LOG_WARNING, "Unable to allocate variable %s\n", name);
-		return 1;
-	}
-
-	return 0;
-}
-
-static int check_vars(void)
-{
-	if (!dbfile) {
-		ast_log(LOG_ERROR, "Required parameter undefined: dbfile\n");
-		return 1;
-	}
-
-	use_cdr = (cdr_table != NULL);
-
-	return 0;
-}
-
-static int load_config(void)
-{
-	struct ast_config *config;
-	struct ast_variable *var;
-	int error;
-	struct ast_flags config_flags = { 0 };
-
-	config = ast_config_load(RES_CONFIG_SQLITE_CONF_FILE, config_flags);
-
-	if (config == CONFIG_STATUS_FILEMISSING || config == CONFIG_STATUS_FILEINVALID) {
-		ast_log(LOG_ERROR, "Unable to load " RES_CONFIG_SQLITE_CONF_FILE "\n");
-		return 1;
-	}
-
-	for (var = ast_variable_browse(config, "general"); var; var = var->next) {
-		if (!strcasecmp(var->name, "dbfile"))
-			SET_VAR(config, dbfile, var);
-		else if (!strcasecmp(var->name, "config_table"))
-			SET_VAR(config, config_table, var);
-		else if (!strcasecmp(var->name, "cdr_table")) {
-			SET_VAR(config, cdr_table, var);
-		} else
-			ast_log(LOG_WARNING, "Unknown parameter : %s\n", var->name);
-	}
-
-	ast_config_destroy(config);
-	error = check_vars();
-
-	if (error) {
-		unload_config();
-		return 1;
-	}
-
-	return 0;
-}
-
-static void unload_config(void)
-{
-	struct sqlite_cache_tables *tbl;
-	ast_free(dbfile);
-	dbfile = NULL;
-	ast_free(config_table);
-	config_table = NULL;
-	ast_free(cdr_table);
-	cdr_table = NULL;
-	AST_RWLIST_WRLOCK(&sqlite_tables);
-	while ((tbl = AST_RWLIST_REMOVE_HEAD(&sqlite_tables, list))) {
-		free_table(tbl);
-	}
-	AST_RWLIST_UNLOCK(&sqlite_tables);
-}
-
-static int cdr_handler(struct ast_cdr *cdr)
-{
-	char *errormsg = NULL, *tmp, workspace[500];
-	int error, scannum;
-	struct sqlite_cache_tables *tbl = find_table(cdr_table);
-	struct sqlite_cache_columns *col;
-	struct ast_str *sql1 = ast_str_create(160), *sql2 = ast_str_create(16);
-	int first = 1;
-
-	if (!sql1 || !sql2) {
-		ast_free(sql1);
-		ast_free(sql2);
-		return -1;
-	}
-
-	if (!tbl) {
-		ast_log(LOG_WARNING, "No such table: %s\n", cdr_table);
-		ast_free(sql1);
-		ast_free(sql2);
-		return -1;
-	}
-
-	ast_str_set(&sql1, 0, "INSERT INTO %s (", cdr_table);
-	ast_str_set(&sql2, 0, ") VALUES (");
-
-	AST_RWLIST_TRAVERSE(&(tbl->columns), col, list) {
-		if (col->isint) {
-			ast_cdr_format_var(cdr, col->name, &tmp, workspace, sizeof(workspace), 1);
-			if (!tmp) {
-				continue;
-			}
-			if (sscanf(tmp, "%30d", &scannum) == 1) {
-				ast_str_append(&sql1, 0, "%s%s", first ? "" : ",", col->name);
-				ast_str_append(&sql2, 0, "%s%d", first ? "" : ",", scannum);
-			}
-		} else {
-			ast_cdr_format_var(cdr, col->name, &tmp, workspace, sizeof(workspace), 0);
-			if (!tmp) {
-				continue;
-			}
-			ast_str_append(&sql1, 0, "%s%s", first ? "" : ",", col->name);
-			tmp = sqlite_mprintf("%Q", tmp);
-			ast_str_append(&sql2, 0, "%s%s", first ? "" : ",", tmp);
-			sqlite_freemem(tmp);
-		}
-		first = 0;
-	}
-	release_table(tbl);
-
-	ast_str_append(&sql1, 0, "%s)", ast_str_buffer(sql2));
-	ast_free(sql2);
-
-	ast_debug(1, "SQL query: %s\n", ast_str_buffer(sql1));
-
-	ast_mutex_lock(&mutex);
-
-	RES_CONFIG_SQLITE_BEGIN
-		error = sqlite_exec(db, ast_str_buffer(sql1), NULL, NULL, &errormsg);
-	RES_CONFIG_SQLITE_END(error)
-
-	ast_mutex_unlock(&mutex);
-
-	ast_free(sql1);
-
-	if (error) {
-		ast_log(LOG_ERROR, "%s\n", S_OR(errormsg, sqlite_error_string(error)));
-		sqlite_freemem(errormsg);
-		return 1;
-	}
-	sqlite_freemem(errormsg);
-
-	return 0;
-}
-
-static int add_cfg_entry(void *arg, int argc, char **argv, char **columnNames)
-{
-	struct cfg_entry_args *args;
-	struct ast_variable *var;
-
-	if (argc != RES_CONFIG_SQLITE_CONFIG_COLUMNS) {
-		ast_log(LOG_WARNING, "Corrupt table\n");
-		return 1;
-	}
-
-	args = arg;
-
-	if (!strcmp(argv[RES_CONFIG_SQLITE_CONFIG_VAR_NAME], "#include")) {
-		struct ast_config *cfg;
-		char *val;
-
-		val = argv[RES_CONFIG_SQLITE_CONFIG_VAR_VAL];
-		cfg = ast_config_internal_load(val, args->cfg, args->flags, "", args->who_asked);
-
-		if (!cfg) {
-			ast_log(LOG_WARNING, "Unable to include %s\n", val);
-			return 1;
-		} else {
-			args->cfg = cfg;
-			return 0;
-		}
-	}
-
-	if (!args->cat_name || strcmp(args->cat_name, argv[RES_CONFIG_SQLITE_CONFIG_CATEGORY])) {
-		args->cat = ast_category_new_dynamic(argv[RES_CONFIG_SQLITE_CONFIG_CATEGORY]);
-		if (!args->cat) {
-			return 1;
-		}
-
-		ast_free(args->cat_name);
-		args->cat_name = ast_strdup(argv[RES_CONFIG_SQLITE_CONFIG_CATEGORY]);
-
-		if (!args->cat_name) {
-			ast_category_destroy(args->cat);
-			return 1;
-		}
-
-		ast_category_append(args->cfg, args->cat);
-	}
-
-	var = ast_variable_new(argv[RES_CONFIG_SQLITE_CONFIG_VAR_NAME], argv[RES_CONFIG_SQLITE_CONFIG_VAR_VAL], "");
-
-	if (!var) {
-		ast_log(LOG_WARNING, "Unable to allocate variable\n");
-		return 1;
-	}
-
-	ast_variable_append(args->cat, var);
-
-	return 0;
-}
-
-static struct ast_config *config_handler(const char *database,	const char *table, const char *file,
-	struct ast_config *cfg, struct ast_flags flags, const char *suggested_incl, const char *who_asked)
-{
-	struct cfg_entry_args args;
-	char *query, *errormsg = NULL;
-	int error;
-
-	if (!config_table) {
-		if (!table) {
-			ast_log(LOG_ERROR, "Table name unspecified\n");
-			return NULL;
-		}
-	} else
-		table = config_table;
-
-	query = sqlite_mprintf(sql_get_config_table, table, file);
-
-	if (!query) {
-		ast_log(LOG_WARNING, "Unable to allocate SQL query\n");
-		return NULL;
-	}
-
-	ast_debug(1, "SQL query: %s\n", query);
-	args.cfg = cfg;
-	args.cat = NULL;
-	args.cat_name = NULL;
-	args.flags = flags;
-	args.who_asked = who_asked;
-
-	ast_mutex_lock(&mutex);
-
-	RES_CONFIG_SQLITE_BEGIN
-		error = sqlite_exec(db, query, add_cfg_entry, &args, &errormsg);
-	RES_CONFIG_SQLITE_END(error)
-
-	ast_mutex_unlock(&mutex);
-
-	ast_free(args.cat_name);
-	sqlite_freemem(query);
-
-	if (error) {
-		ast_log(LOG_ERROR, "%s\n", S_OR(errormsg, sqlite_error_string(error)));
-		sqlite_freemem(errormsg);
-		return NULL;
-	}
-	sqlite_freemem(errormsg);
-
-	return cfg;
-}
-
-static int add_rt_cfg_entry(void *arg, int argc, char **argv, char **columnNames)
-{
-	struct rt_cfg_entry_args *args;
-	struct ast_variable *var;
-	int i;
-
-	args = arg;
-
-	for (i = 0; i < argc; i++) {
-		if (!argv[i])
-			continue;
-
-		if (!(var = ast_variable_new(columnNames[i], argv[i], "")))
-			return 1;
-
-		if (!args->var)
-			args->var = var;
-
-		if (!args->last)
-			args->last = var;
-		else {
-			args->last->next = var;
-			args->last = var;
-		}
-	}
-
-	return 0;
-}
-
-static struct ast_variable * realtime_handler(const char *database, const char *table, const struct ast_variable *fields)
-{
-	char *query, *errormsg = NULL, *op, *tmp_str;
-	struct rt_cfg_entry_args args;
-	const struct ast_variable *field = fields;
-	int error;
-
-	if (!table) {
-		ast_log(LOG_WARNING, "Table name unspecified\n");
-		return NULL;
-	}
-
-	if (!fields) {
-		return NULL;
-	}
-
-	op = (strchr(field->name, ' ') == NULL) ? " =" : "";
-
-/* \cond DOXYGEN_CAN_PARSE_THIS */
-#undef QUERY
-#define QUERY "SELECT * FROM '%q' WHERE%s %q%s '%q'"
-/* \endcond */
-
-	query = sqlite_mprintf(QUERY, table, (config_table && !strcmp(config_table, table)) ? " commented = 0 AND" : "", field->name, op, field->value);
-
-	if (!query) {
-		ast_log(LOG_WARNING, "Unable to allocate SQL query\n");
-		return NULL;
-	}
-
-	while ((field = field->next)) {
-		op = (strchr(field->name, ' ') == NULL) ? " =" : "";
-		tmp_str = sqlite_mprintf("%s AND %q%s '%q'", query, field->name, op, field->value);
-		sqlite_freemem(query);
-
-		if (!tmp_str) {
-			ast_log(LOG_WARNING, "Unable to reallocate SQL query\n");
-			return NULL;
-		}
-
-		query = tmp_str;
-	}
-
-	tmp_str = sqlite_mprintf("%s LIMIT 1;", query);
-	sqlite_freemem(query);
-
-	if (!tmp_str) {
-		ast_log(LOG_WARNING, "Unable to reallocate SQL query\n");
-		return NULL;
-	}
-
-	query = tmp_str;
-	ast_debug(1, "SQL query: %s\n", query);
-	args.var = NULL;
-	args.last = NULL;
-
-	ast_mutex_lock(&mutex);
-
-	RES_CONFIG_SQLITE_BEGIN
-		error = sqlite_exec(db, query, add_rt_cfg_entry, &args, &errormsg);
-	RES_CONFIG_SQLITE_END(error)
-
-	ast_mutex_unlock(&mutex);
-
-	sqlite_freemem(query);
-
-	if (error) {
-		ast_log(LOG_WARNING, "%s\n", S_OR(errormsg, sqlite_error_string(error)));
-		sqlite_freemem(errormsg);
-		ast_variables_destroy(args.var);
-		return NULL;
-	}
-	sqlite_freemem(errormsg);
-
-	return args.var;
-}
-
-static int add_rt_multi_cfg_entry(void *arg, int argc, char **argv, char **columnNames)
-{
-	struct rt_multi_cfg_entry_args *args;
-	struct ast_category *cat;
-	struct ast_variable *var;
-	char *cat_name;
-	size_t i;
-
-	args = arg;
-	cat_name = NULL;
-
-	/*
-	 * cat_name should always be set here, since initfield is forged from
-	 * params[0] in realtime_multi_handler(), which is a search parameter
-	 * of the SQL query.
-	 */
-	for (i = 0; i < argc; i++) {
-		if (!strcmp(args->initfield, columnNames[i]))
-			cat_name = argv[i];
-	}
-
-	if (!cat_name) {
-		ast_log(LOG_ERROR, "Bogus SQL results, cat_name is NULL !\n");
-		return 1;
-	}
-
-	cat = ast_category_new_dynamic(cat_name);
-	if (!cat) {
-		return 1;
-	}
-
-	ast_category_append(args->cfg, cat);
-
-	for (i = 0; i < argc; i++) {
-		if (!argv[i]) {
-			continue;
-		}
-
-		if (!(var = ast_variable_new(columnNames[i], argv[i], ""))) {
-			ast_log(LOG_WARNING, "Unable to allocate variable\n");
-			return 1;
-		}
-
-		ast_variable_append(cat, var);
-	}
-
-	return 0;
-}
-
-static struct ast_config *realtime_multi_handler(const char *database,
-	const char *table, const struct ast_variable *fields)
-{
-	char *query, *errormsg = NULL, *op, *tmp_str, *initfield;
-	struct rt_multi_cfg_entry_args args;
-	const struct ast_variable *field = fields;
-	struct ast_config *cfg;
-	int error;
-
-	if (!table) {
-		ast_log(LOG_WARNING, "Table name unspecified\n");
-		return NULL;
-	}
-
-	if (!fields) {
-		return NULL;
-	}
-
-	if (!(cfg = ast_config_new())) {
-		ast_log(LOG_WARNING, "Unable to allocate configuration structure\n");
-		return NULL;
-	}
-
-	if (!(initfield = ast_strdup(field->name))) {
-		ast_config_destroy(cfg);
-		return NULL;
-	}
-
-	tmp_str = strchr(initfield, ' ');
-
-	if (tmp_str)
-		*tmp_str = '\0';
-
-	op = (!strchr(field->name, ' ')) ? " =" : "";
-
-	/*
-	 * Asterisk sends us an already escaped string when searching for
-	 * "exten LIKE" (uh!). Handle it separately.
-	 */
-	tmp_str = (!strcmp(field->value, "\\_%")) ? "_%" : (char *)field->value;
-
-/* \cond DOXYGEN_CAN_PARSE_THIS */
-#undef QUERY
-#define QUERY "SELECT * FROM '%q' WHERE%s %q%s '%q'"
-/* \endcond */
-
-	if (!(query = sqlite_mprintf(QUERY, table, (config_table && !strcmp(config_table, table)) ? " commented = 0 AND" : "", field->name, op, tmp_str))) {
-		ast_log(LOG_WARNING, "Unable to allocate SQL query\n");
-		ast_config_destroy(cfg);
-		ast_free(initfield);
-		return NULL;
-	}
-
-	while ((field = field->next)) {
-		op = (!strchr(field->name, ' ')) ? " =" : "";
-		tmp_str = sqlite_mprintf("%s AND %q%s '%q'", query, field->name, op, field->value);
-		sqlite_freemem(query);
-
-		if (!tmp_str) {
-			ast_log(LOG_WARNING, "Unable to reallocate SQL query\n");
-			ast_config_destroy(cfg);
-			ast_free(initfield);
-			return NULL;
-		}
-
-		query = tmp_str;
-	}
-
-	if (!(tmp_str = sqlite_mprintf("%s ORDER BY %q;", query, initfield))) {
-		ast_log(LOG_WARNING, "Unable to reallocate SQL query\n");
-		sqlite_freemem(query);
-		ast_config_destroy(cfg);
-		ast_free(initfield);
-		return NULL;
-	}
-
-	sqlite_freemem(query);
-	query = tmp_str;
-	ast_debug(1, "SQL query: %s\n", query);
-	args.cfg = cfg;
-	args.initfield = initfield;
-
-	ast_mutex_lock(&mutex);
-
-	RES_CONFIG_SQLITE_BEGIN
-		error = sqlite_exec(db, query, add_rt_multi_cfg_entry, &args, &errormsg);
-	RES_CONFIG_SQLITE_END(error)
-
-	ast_mutex_unlock(&mutex);
-
-	sqlite_freemem(query);
-	ast_free(initfield);
-
-	if (error) {
-		ast_log(LOG_WARNING, "%s\n", S_OR(errormsg, sqlite_error_string(error)));
-		sqlite_freemem(errormsg);
-		ast_config_destroy(cfg);
-		return NULL;
-	}
-	sqlite_freemem(errormsg);
-
-	return cfg;
-}
-
-static int realtime_update_handler(const char *database, const char *table,
-	const char *keyfield, const char *entity, const struct ast_variable *fields)
-{
-	char *query, *errormsg = NULL, *tmp_str;
-	const struct ast_variable *field = fields;
-	int error, rows_num;
-
-	if (!table) {
-		ast_log(LOG_WARNING, "Table name unspecified\n");
-		return -1;
-	}
-
-	if (!field) {
-		return -1;
-	}
-
-/* \cond DOXYGEN_CAN_PARSE_THIS */
-#undef QUERY
-#define QUERY "UPDATE '%q' SET %q = '%q'"
-/* \endcond */
-
-	if (!(query = sqlite_mprintf(QUERY, table, field->name, field->value))) {
-		ast_log(LOG_WARNING, "Unable to allocate SQL query\n");
-		return -1;
-	}
-
-	while ((field = field->next)) {
-		tmp_str = sqlite_mprintf("%s, %q = '%q'", query, field->name, field->value);
-		sqlite_freemem(query);
-
-		if (!tmp_str) {
-			ast_log(LOG_WARNING, "Unable to reallocate SQL query\n");
-			return -1;
-		}
-
-		query = tmp_str;
-	}
-
-	if (!(tmp_str = sqlite_mprintf("%s WHERE %q = '%q';", query, keyfield, entity))) {
-		ast_log(LOG_WARNING, "Unable to reallocate SQL query\n");
-		sqlite_freemem(query);
-		return -1;
-	}
-
-	sqlite_freemem(query);
-	query = tmp_str;
-	ast_debug(1, "SQL query: %s\n", query);
-
-	ast_mutex_lock(&mutex);
-
-	RES_CONFIG_SQLITE_BEGIN
-		error = sqlite_exec(db, query, NULL, NULL, &errormsg);
-	RES_CONFIG_SQLITE_END(error)
-
-	if (!error)
-		rows_num = sqlite_changes(db);
-	else
-		rows_num = -1;
-
-	ast_mutex_unlock(&mutex);
-
-	sqlite_freemem(query);
-
-	if (error) {
-		ast_log(LOG_WARNING, "%s\n", S_OR(errormsg, sqlite_error_string(error)));
-	}
-	sqlite_freemem(errormsg);
-
-	return rows_num;
-}
-
-static int realtime_update2_handler(const char *database, const char *table,
-	const struct ast_variable *lookup_fields, const struct ast_variable *update_fields)
-{
-	char *errormsg = NULL, *tmp1, *tmp2;
-	int error, rows_num, first = 1;
-	struct ast_str *sql = ast_str_thread_get(&sql_buf, 100);
-	struct ast_str *where = ast_str_thread_get(&where_buf, 100);
-	const struct ast_variable *field;
-
-	if (!table) {
-		ast_log(LOG_WARNING, "Table name unspecified\n");
-		return -1;
-	}
-
-	if (!sql) {
-		return -1;
-	}
-
-	ast_str_set(&sql, 0, "UPDATE %s SET", table);
-	ast_str_set(&where, 0, " WHERE");
-
-	for (field = lookup_fields; field; field = field->next) {
-		ast_str_append(&where, 0, "%s %s = %s",
-			first ? "" : " AND",
-			tmp1 = sqlite_mprintf("%q", field->name),
-			tmp2 = sqlite_mprintf("%Q", field->value));
-		sqlite_freemem(tmp1);
-		sqlite_freemem(tmp2);
-		first = 0;
-	}
-
-	if (first) {
-		ast_log(LOG_ERROR, "No criteria specified on update to '%s@%s'!\n", table, database);
-		return -1;
-	}
-
-	first = 1;
-	for (field = update_fields; field; field = field->next) {
-		ast_str_append(&sql, 0, "%s %s = %s",
-			first ? "" : ",",
-			tmp1 = sqlite_mprintf("%q", field->name),
-			tmp2 = sqlite_mprintf("%Q", field->value));
-		sqlite_freemem(tmp1);
-		sqlite_freemem(tmp2);
-		first = 0;
-	}
-
-	ast_str_append(&sql, 0, " %s", ast_str_buffer(where));
-	ast_debug(1, "SQL query: %s\n", ast_str_buffer(sql));
-
-	ast_mutex_lock(&mutex);
-
-	RES_CONFIG_SQLITE_BEGIN
-		error = sqlite_exec(db, ast_str_buffer(sql), NULL, NULL, &errormsg);
-	RES_CONFIG_SQLITE_END(error)
-
-	if (!error) {
-		rows_num = sqlite_changes(db);
-	} else {
-		rows_num = -1;
-	}
-
-	ast_mutex_unlock(&mutex);
-
-	if (error) {
-		ast_log(LOG_WARNING, "%s\n", S_OR(errormsg, sqlite_error_string(error)));
-	}
-	sqlite_freemem(errormsg);
-
-	return rows_num;
-}
-
-static int realtime_store_handler(const char *database, const char *table, const struct ast_variable *fields)
-{
-	char *errormsg = NULL, *tmp_str, *tmp_keys = NULL, *tmp_keys2 = NULL, *tmp_vals = NULL, *tmp_vals2 = NULL;
-	const struct ast_variable *field = fields;
-	int error, rows_id;
-
-	if (!table) {
-		ast_log(LOG_WARNING, "Table name unspecified\n");
-		return -1;
-	}
-
-	if (!fields) {
-		return -1;
-	}
-
-/* \cond DOXYGEN_CAN_PARSE_THIS */
-#undef QUERY
-#define QUERY "INSERT into '%q' (%s) VALUES (%s);"
-/* \endcond */
-
-	do {
-		if ( tmp_keys2 ) {
-			tmp_keys = sqlite_mprintf("%s, %q", tmp_keys2, field->name);
-			sqlite_freemem(tmp_keys2);
-		} else {
-			tmp_keys = sqlite_mprintf("%q", field->name);
-		}
-		if (!tmp_keys) {
-			ast_log(LOG_WARNING, "Unable to reallocate SQL query\n");
-			sqlite_freemem(tmp_vals);
-			return -1;
-		}
-
-		if ( tmp_vals2 ) {
-			tmp_vals = sqlite_mprintf("%s, '%q'", tmp_vals2, field->value);
-			sqlite_freemem(tmp_vals2);
-		} else {
-			tmp_vals = sqlite_mprintf("'%q'", field->value);
-		}
-		if (!tmp_vals) {
-			ast_log(LOG_WARNING, "Unable to reallocate SQL query\n");
-			sqlite_freemem(tmp_keys);
-			return -1;
-		}
-
-
-		tmp_keys2 = tmp_keys;
-		tmp_vals2 = tmp_vals;
-	} while ((field = field->next));
-
-	if (!(tmp_str = sqlite_mprintf(QUERY, table, tmp_keys, tmp_vals))) {
-		ast_log(LOG_WARNING, "Unable to reallocate SQL query\n");
-		sqlite_freemem(tmp_keys);
-		sqlite_freemem(tmp_vals);
-		return -1;
-	}
-
-	sqlite_freemem(tmp_keys);
-	sqlite_freemem(tmp_vals);
-
-	ast_debug(1, "SQL query: %s\n", tmp_str);
-
-	ast_mutex_lock(&mutex);
-
-	RES_CONFIG_SQLITE_BEGIN
-		error = sqlite_exec(db, tmp_str, NULL, NULL, &errormsg);
-	RES_CONFIG_SQLITE_END(error)
-
-	if (!error) {
-		rows_id = sqlite_last_insert_rowid(db);
-	} else {
-		rows_id = -1;
-	}
-
-	ast_mutex_unlock(&mutex);
-
-	sqlite_freemem(tmp_str);
-
-	if (error) {
-		ast_log(LOG_WARNING, "%s\n", S_OR(errormsg, sqlite_error_string(error)));
-	}
-	sqlite_freemem(errormsg);
-
-	return rows_id;
-}
-
-static int realtime_destroy_handler(const char *database, const char *table,
-	const char *keyfield, const char *entity, const struct ast_variable *fields)
-{
-	char *query, *errormsg = NULL, *tmp_str;
-	const struct ast_variable *field;
-	int error, rows_num;
-
-	if (!table) {
-		ast_log(LOG_WARNING, "Table name unspecified\n");
-		return -1;
-	}
-
-/* \cond DOXYGEN_CAN_PARSE_THIS */
-#undef QUERY
-#define QUERY "DELETE FROM '%q' WHERE"
-/* \endcond */
-
-	if (!(query = sqlite_mprintf(QUERY, table))) {
-		ast_log(LOG_WARNING, "Unable to allocate SQL query\n");
-		return -1;
-	}
-
-	for (field = fields; field; field = field->next) {
-		tmp_str = sqlite_mprintf("%s %q = '%q' AND", query, field->name, field->value);
-		sqlite_freemem(query);
-
-		if (!tmp_str) {
-			ast_log(LOG_WARNING, "Unable to reallocate SQL query\n");
-			return -1;
-		}
-
-		query = tmp_str;
-	}
-
-	if (!(tmp_str = sqlite_mprintf("%s %q = '%q';", query, keyfield, entity))) {
-		ast_log(LOG_WARNING, "Unable to reallocate SQL query\n");
-		sqlite_freemem(query);
-		return -1;
-	}
-	sqlite_freemem(query);
-	query = tmp_str;
-	ast_debug(1, "SQL query: %s\n", query);
-
-	ast_mutex_lock(&mutex);
-
-	RES_CONFIG_SQLITE_BEGIN
-		error = sqlite_exec(db, query, NULL, NULL, &errormsg);
-	RES_CONFIG_SQLITE_END(error)
-
-	if (!error) {
-		rows_num = sqlite_changes(db);
-	} else {
-		rows_num = -1;
-	}
-
-	ast_mutex_unlock(&mutex);
-
-	sqlite_freemem(query);
-
-	if (error) {
-		ast_log(LOG_WARNING, "%s\n", S_OR(errormsg, sqlite_error_string(error)));
-	}
-	sqlite_freemem(errormsg);
-
-	return rows_num;
-}
-
-static int realtime_require_handler(const char *unused, const char *tablename, va_list ap)
-{
-	struct sqlite_cache_tables *tbl = find_table(tablename);
-	struct sqlite_cache_columns *col;
-	char *elm;
-	int type, res = 0;
-
-	if (!tbl) {
-		return -1;
-	}
-
-	while ((elm = va_arg(ap, char *))) {
-		type = va_arg(ap, require_type);
-		va_arg(ap, int);
-		/* Check if the field matches the criteria */
-		AST_RWLIST_TRAVERSE(&tbl->columns, col, list) {
-			if (strcmp(col->name, elm) == 0) {
-				/* SQLite only has two types - the 32-bit integer field that
-				 * is the key column, and everything else (everything else
-				 * being a string).
-				 */
-				if (col->isint && !ast_rq_is_int(type)) {
-					ast_log(LOG_WARNING, "Realtime table %s: column '%s' is an integer field, but Asterisk requires that it not be!\n", tablename, col->name);
-					res = -1;
-				}
-				break;
-			}
-		}
-		if (!col) {
-			ast_log(LOG_WARNING, "Realtime table %s requires column '%s', but that column does not exist!\n", tablename, elm);
-		}
-	}
-	AST_RWLIST_UNLOCK(&(tbl->columns));
-	return res;
-}
-
-static int realtime_unload_handler(const char *unused, const char *tablename)
-{
-	struct sqlite_cache_tables *tbl;
-	AST_RWLIST_WRLOCK(&sqlite_tables);
-	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&sqlite_tables, tbl, list) {
-		if (!strcasecmp(tbl->name, tablename)) {
-			AST_RWLIST_REMOVE_CURRENT(list);
-			free_table(tbl);
-		}
-	}
-	AST_RWLIST_TRAVERSE_SAFE_END
-	AST_RWLIST_UNLOCK(&sqlite_tables);
-	return 0;
-}
-
-static char *handle_cli_show_sqlite_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "sqlite show status";
-		e->usage =
-			"Usage: sqlite show status\n"
-			"       Show status information about the SQLite 2 driver\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 3)
-		return CLI_SHOWUSAGE;
-
-	ast_cli(a->fd, "SQLite database path: %s\n", dbfile);
-	ast_cli(a->fd, "config_table: ");
-
-	if (!config_table)
-		ast_cli(a->fd, "unspecified, must be present in extconfig.conf\n");
-	else
-		ast_cli(a->fd, "%s\n", config_table);
-
-	ast_cli(a->fd, "cdr_table: ");
-
-	if (!cdr_table)
-		ast_cli(a->fd, "unspecified, CDR support disabled\n");
-	else
-		ast_cli(a->fd, "%s\n", cdr_table);
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_cli_sqlite_show_tables(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct sqlite_cache_tables *tbl;
-	struct sqlite_cache_columns *col;
-	int found = 0;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "sqlite show tables";
-		e->usage =
-			"Usage: sqlite show tables\n"
-			"       Show table information about the SQLite 2 driver\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 3)
-		return CLI_SHOWUSAGE;
-
-	AST_RWLIST_RDLOCK(&sqlite_tables);
-	AST_RWLIST_TRAVERSE(&sqlite_tables, tbl, list) {
-		found++;
-		ast_cli(a->fd, "Table %s:\n", tbl->name);
-		AST_RWLIST_TRAVERSE(&(tbl->columns), col, list) {
-			fprintf(stderr, "%s\n", col->name);
-			ast_cli(a->fd, "  %20.20s  %-30.30s\n", col->name, col->type);
-		}
-	}
-	AST_RWLIST_UNLOCK(&sqlite_tables);
-
-	if (!found) {
-		ast_cli(a->fd, "No tables currently in cache\n");
-	}
-
-	return CLI_SUCCESS;
-}
-
-static int unload_module(void)
-{
-	if (cdr_registered && ast_cdr_unregister(RES_CONFIG_SQLITE_NAME)) {
-		return -1;
-	}
-
-	if (cli_status_registered) {
-		ast_cli_unregister_multiple(cli_status, ARRAY_LEN(cli_status));
-	}
-
-	ast_config_engine_deregister(&sqlite_engine);
-
-	if (db)
-		sqlite_close(db);
-
-	unload_config();
-
-	return 0;
-}
-
-/*!
- * \brief Load the module
- *
- * Module loading including tests for configuration or dependencies.
- * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
- * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
- * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the
- * configuration file or other non-critical problem return
- * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
- */
-static int load_module(void)
-{
-	char *errormsg = NULL;
-	int error;
-
-	db = NULL;
-	cdr_registered = 0;
-	cli_status_registered = 0;
-	dbfile = NULL;
-	config_table = NULL;
-	cdr_table = NULL;
-	error = load_config();
-
-	if (error)
-		return AST_MODULE_LOAD_DECLINE;
-
-	if (!(db = sqlite_open(dbfile, 0660, &errormsg))) {
-		ast_log(LOG_ERROR, "%s\n", S_OR(errormsg, sqlite_error_string(error)));
-		sqlite_freemem(errormsg);
-		unload_module();
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	sqlite_freemem(errormsg);
-	errormsg = NULL;
-	ast_config_engine_register(&sqlite_engine);
-
-	if (use_cdr) {
-		char *query;
-
-/* \cond DOXYGEN_CAN_PARSE_THIS */
-#undef QUERY
-#define QUERY "SELECT COUNT(id) FROM %Q;"
-/* \endcond */
-
-		query = sqlite_mprintf(QUERY, cdr_table);
-
-		if (!query) {
-			ast_log(LOG_ERROR, "Unable to allocate SQL query\n");
-			unload_module();
-			return AST_MODULE_LOAD_DECLINE;
-		}
-
-		ast_debug(1, "SQL query: %s\n", query);
-
-		RES_CONFIG_SQLITE_BEGIN
-			error = sqlite_exec(db, query, NULL, NULL, &errormsg);
-		RES_CONFIG_SQLITE_END(error)
-
-		sqlite_freemem(query);
-
-		if (error) {
-			/*
-			 * Unexpected error.
-			 */
-			if (error != SQLITE_ERROR) {
-				ast_log(LOG_ERROR, "%s\n", S_OR(errormsg, sqlite_error_string(error)));
-				sqlite_freemem(errormsg);
-				unload_module();
-				return AST_MODULE_LOAD_DECLINE;
-			}
-
-			sqlite_freemem(errormsg);
-			errormsg = NULL;
-			query = sqlite_mprintf(sql_create_cdr_table, cdr_table);
-
-			if (!query) {
-				ast_log(LOG_ERROR, "Unable to allocate SQL query\n");
-				unload_module();
-				return AST_MODULE_LOAD_DECLINE;
-			}
-
-			ast_debug(1, "SQL query: %s\n", query);
-
-			RES_CONFIG_SQLITE_BEGIN
-				error = sqlite_exec(db, query, NULL, NULL, &errormsg);
-			RES_CONFIG_SQLITE_END(error)
-
-			sqlite_freemem(query);
-
-			if (error) {
-				ast_log(LOG_ERROR, "%s\n", S_OR(errormsg, sqlite_error_string(error)));
-				sqlite_freemem(errormsg);
-				unload_module();
-				return AST_MODULE_LOAD_DECLINE;
-			}
-		}
-		sqlite_freemem(errormsg);
-		errormsg = NULL;
-
-		error = ast_cdr_register(RES_CONFIG_SQLITE_NAME, RES_CONFIG_SQLITE_DESCRIPTION, cdr_handler);
-
-		if (error) {
-			unload_module();
-			return AST_MODULE_LOAD_DECLINE;
-		}
-
-		cdr_registered = 1;
-	}
-
-	error = ast_cli_register_multiple(cli_status, ARRAY_LEN(cli_status));
-
-	if (error) {
-		unload_module();
-		return AST_MODULE_LOAD_DECLINE;
-	}
-
-	cli_status_registered = 1;
-
-	return AST_MODULE_LOAD_SUCCESS;
-}
-
-/*
- * This module should require "cdr" to enforce startup/shutdown ordering but it
- * loads at REALTIME_DRIVER priority which would cause "cdr" to load too early.
- *
- * ast_cdr_register / ast_cdr_unregister is safe for use while "cdr" is not running.
- */
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Realtime SQLite configuration",
-	.support_level = AST_MODULE_SUPPORT_DEPRECATED,
-	.load = load_module,
-	.unload = unload_module,
-	.load_pri = AST_MODPRI_REALTIME_DRIVER,
-	.requires = "extconfig",
-);
diff --git a/res/res_config_sqlite3.c b/res/res_config_sqlite3.c
index 5dc4784621392f3e980f535e7142c49b1778e6c0..3c3de53ae0218f4772c258bd20d4802dcf702b68 100644
--- a/res/res_config_sqlite3.c
+++ b/res/res_config_sqlite3.c
@@ -14,9 +14,6 @@
  * This program is free software, distributed under the terms of
  * the GNU General Public License Version 2. See the LICENSE file
  * at the top of the source tree.
- *
- * Please follow coding guidelines
- * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES
  */
 
 /*! \file
diff --git a/res/res_crypto.c b/res/res_crypto.c
index 65ebcc5011121f11328444748e9d9c128f788411..2f7868cb625c9e92bc1fdfb38f94f96f3eb767ec 100644
--- a/res/res_crypto.c
+++ b/res/res_crypto.c
@@ -34,13 +34,13 @@
 #include "asterisk.h"
 
 #include <dirent.h>                 /* for closedir, opendir, readdir, DIR */
+#include <sys/stat.h>               /* for fstat */
 
-#include <openssl/aes.h>            /* for AES_decrypt, AES_encrypt, AES_set... */
 #include <openssl/err.h>            /* for ERR_print_errors_fp */
 #include <openssl/ssl.h>            /* for NID_sha1, RSA */
-#include <openssl/pem.h>            /* for PEM_read_RSAPrivateKey, PEM_read_... */
-#include <openssl/rsa.h>            /* for RSA_free, RSA_private_decrypt, RSA */
-#include <openssl/sha.h>            /* for SHA1 */
+#include <openssl/evp.h>            /* for EVP_PKEY, EVP_sha1(), ... */
+#include <openssl/md5.h>            /* for MD5_DIGEST_LENGTH */
+#include <openssl/sha.h>            /* for SHA_DIGEST_LENGTH */
 
 #include "asterisk/cli.h"           /* for ast_cli, ast_cli_args, ast_cli_entry */
 #include "asterisk/compat.h"        /* for strcasecmp */
@@ -52,6 +52,7 @@
 #include "asterisk/options.h"       /* for ast_opt_init_keys */
 #include "asterisk/paths.h"         /* for ast_config_AST_KEY_DIR */
 #include "asterisk/utils.h"         /* for ast_copy_string, ast_base64decode */
+#include "asterisk/file.h"          /* for ast_file_read_dirs */
 
 #define AST_API_MODULE
 #include "asterisk/crypto.h"        /* for AST_KEY_PUBLIC, AST_KEY_PRIVATE */
@@ -71,6 +72,11 @@
 
 #define KEY_NEEDS_PASSCODE (1 << 16)
 
+/* From RFC-2437, section 9.1.1 the padding size is 1+2*hLen, where
+ * the hLen for SHA-1 is 20 bytes (or 160 bits).
+ */
+#define RSA_PKCS1_OAEP_PADDING_SIZE		(1 + 2 * SHA_DIGEST_LENGTH)
+
 struct ast_key {
 	/*! Name of entity */
 	char name[80];
@@ -78,8 +84,8 @@ struct ast_key {
 	char fn[256];
 	/*! Key type (AST_KEY_PUB or AST_KEY_PRIV, along with flags from above) */
 	int ktype;
-	/*! RSA structure (if successfully loaded) */
-	RSA *rsa;
+	/*! RSA key structure (if successfully loaded) */
+	EVP_PKEY *pkey;
 	/*! Whether we should be deleted */
 	int delme;
 	/*! FD for input (or -1 if no input allowed, or -2 if we needed input) */
@@ -87,12 +93,14 @@ struct ast_key {
 	/*! FD for output */
 	int outfd;
 	/*! Last MD5 Digest */
-	unsigned char digest[16];
+	unsigned char digest[MD5_DIGEST_LENGTH];
 	AST_RWLIST_ENTRY(ast_key) list;
 };
 
 static AST_RWLIST_HEAD_STATIC(keys, ast_key);
 
+static void crypto_load(int ifd, int ofd);
+
 /*!
  * \brief setting of priv key
  * \param buf
@@ -166,18 +174,22 @@ struct ast_key * AST_OPTIONAL_API_NAME(ast_key_get)(const char *kname, int ktype
 */
 static struct ast_key *try_load_key(const char *dir, const char *fname, int ifd, int ofd, int *not2)
 {
-	int ktype = 0, found = 0;
-	char *c = NULL, ffname[256];
-	unsigned char digest[16];
+	int n, ktype = 0, found = 0;
+	const char *c = NULL;
+	char ffname[256];
+	unsigned char digest[MD5_DIGEST_LENGTH];
+	unsigned digestlen;
 	FILE *f;
-	struct MD5Context md5;
+	EVP_MD_CTX *ctx = NULL;
 	struct ast_key *key;
 	static int notice = 0;
+	struct stat st;
+	size_t fnamelen = strlen(fname);
 
 	/* Make sure its name is a public or private key */
-	if ((c = strstr(fname, ".pub")) && !strcmp(c, ".pub")) {
+	if (fnamelen > 4 && !strcmp((c = &fname[fnamelen - 4]), ".pub")) {
 		ktype = AST_KEY_PUBLIC;
-	} else if ((c = strstr(fname, ".key")) && !strcmp(c, ".key")) {
+	} else if (fnamelen > 4 && !strcmp((c = &fname[fnamelen - 4]), ".key")) {
 		ktype = AST_KEY_PRIVATE;
 	} else {
 		return NULL;
@@ -192,7 +204,35 @@ static struct ast_key *try_load_key(const char *dir, const char *fname, int ifd,
 		return NULL;
 	}
 
-	MD5Init(&md5);
+	n = fstat(fileno(f), &st);
+	if (n != 0) {
+		ast_log(LOG_ERROR, "Unable to stat key file: %s: %s\n", ffname, strerror(errno));
+		fclose(f);
+		return NULL;
+	}
+
+	if (!S_ISREG(st.st_mode)) {
+		ast_log(LOG_ERROR, "Key file is not a regular file: %s\n", ffname);
+		fclose(f);
+		return NULL;
+	}
+
+	/* only user read or read/write modes allowed */
+	if (ktype == AST_KEY_PRIVATE &&
+	    ((st.st_mode & ALLPERMS) & ~(S_IRUSR | S_IWUSR)) != 0) {
+		ast_log(LOG_ERROR, "Private key file has bad permissions: %s: %#4o\n", ffname, st.st_mode & ALLPERMS);
+		fclose(f);
+		return NULL;
+	}
+
+	ctx = EVP_MD_CTX_create();
+	if (ctx == NULL) {
+		ast_log(LOG_ERROR, "Out of memory\n");
+		fclose(f);
+		return NULL;
+	}
+	EVP_DigestInit(ctx, EVP_md5());
+
 	while (!feof(f)) {
 		/* Calculate a "whatever" quality md5sum of the key */
 		char buf[256] = "";
@@ -200,10 +240,11 @@ static struct ast_key *try_load_key(const char *dir, const char *fname, int ifd,
 			continue;
 		}
 		if (!feof(f)) {
-			MD5Update(&md5, (unsigned char *) buf, strlen(buf));
+			EVP_DigestUpdate(ctx, (unsigned char *)buf, strlen(buf));
 		}
 	}
-	MD5Final(digest, &md5);
+	EVP_DigestFinal(ctx, digest, &digestlen);
+	EVP_MD_CTX_destroy(ctx);
 
 	/* Look for an existing key */
 	AST_RWLIST_TRAVERSE(&keys, key, list) {
@@ -215,7 +256,7 @@ static struct ast_key *try_load_key(const char *dir, const char *fname, int ifd,
 	if (key) {
 		/* If the MD5 sum is the same, and it isn't awaiting a passcode
 		   then this is far enough */
-		if (!memcmp(digest, key->digest, 16) &&
+		if (!memcmp(digest, key->digest, sizeof(digest)) &&
 		    !(key->ktype & KEY_NEEDS_PASSCODE)) {
 			fclose(f);
 			key->delme = 0;
@@ -228,8 +269,6 @@ static struct ast_key *try_load_key(const char *dir, const char *fname, int ifd,
 		}
 	}
 
-	/* Make fname just be the normal name now */
-	*c = '\0';
 	if (!key) {
 		if (!(key = ast_calloc(1, sizeof(*key)))) {
 			fclose(f);
@@ -238,13 +277,13 @@ static struct ast_key *try_load_key(const char *dir, const char *fname, int ifd,
 	}
 	/* First the filename */
 	ast_copy_string(key->fn, ffname, sizeof(key->fn));
-	/* Then the name */
-	ast_copy_string(key->name, fname, sizeof(key->name));
+	/* Then the name minus the suffix */
+	snprintf(key->name, sizeof(key->name), "%.*s", (int)(c - fname), fname);
 	key->ktype = ktype;
 	/* Yes, assume we're going to be deleted */
 	key->delme = 1;
 	/* Keep the key type */
-	memcpy(key->digest, digest, 16);
+	memcpy(key->digest, digest, sizeof(key->digest));
 	/* Can I/O takes the FD we're given */
 	key->infd = ifd;
 	key->outfd = ofd;
@@ -252,13 +291,13 @@ static struct ast_key *try_load_key(const char *dir, const char *fname, int ifd,
 	rewind(f);
 	/* Now load the key with the right method */
 	if (ktype == AST_KEY_PUBLIC) {
-		key->rsa = PEM_read_RSA_PUBKEY(f, NULL, pw_cb, key);
+		PEM_read_PUBKEY(f, &key->pkey, pw_cb, key);
 	} else {
-		key->rsa = PEM_read_RSAPrivateKey(f, NULL, pw_cb, key);
+		PEM_read_PrivateKey(f, &key->pkey, pw_cb, key);
 	}
 	fclose(f);
-	if (key->rsa) {
-		if (RSA_size(key->rsa) == 128) {
+	if (key->pkey) {
+		if (EVP_PKEY_size(key->pkey) == (AST_CRYPTO_RSA_KEY_BITS / 8)) {
 			/* Key loaded okay */
 			key->ktype &= ~KEY_NEEDS_PASSCODE;
 			ast_verb(3, "Loaded %s key '%s'\n", key->ktype == AST_KEY_PUBLIC ? "PUBLIC" : "PRIVATE", key->name);
@@ -268,7 +307,7 @@ static struct ast_key *try_load_key(const char *dir, const char *fname, int ifd,
 			ast_log(LOG_NOTICE, "Key '%s' is not expected size.\n", key->name);
 		}
 	} else if (key->infd != -2) {
-		ast_log(LOG_WARNING, "Key load %s '%s' failed\n",key->ktype == AST_KEY_PUBLIC ? "PUBLIC" : "PRIVATE", key->name);
+		ast_log(LOG_WARNING, "Key load %s '%s' failed\n", key->ktype == AST_KEY_PUBLIC ? "PUBLIC" : "PRIVATE", key->name);
 		if (ofd > -1) {
 			ERR_print_errors_fp(stderr);
 		} else {
@@ -297,97 +336,231 @@ static struct ast_key *try_load_key(const char *dir, const char *fname, int ifd,
 	return key;
 }
 
+static int evp_pkey_sign(EVP_PKEY *pkey, const unsigned char *in, unsigned inlen, unsigned char *sig, unsigned *siglen, unsigned padding)
+{
+	EVP_PKEY_CTX *ctx = NULL;
+	int res = -1;
+	size_t _siglen;
+
+	if (*siglen < EVP_PKEY_size(pkey)) {
+		return -1;
+	}
+
+	if ((ctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL) {
+		return -1;
+	}
+
+	do {
+		if ((res = EVP_PKEY_sign_init(ctx)) <= 0) {
+			break;
+		}
+		if ((res = EVP_PKEY_CTX_set_rsa_padding(ctx, padding)) <= 0) {
+			break;
+		}
+		if ((res = EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1())) <= 0) {
+			break;
+		}
+		_siglen = *siglen;
+		if ((res = EVP_PKEY_sign(ctx, sig, &_siglen, in, inlen)) <= 0) {
+			break;
+		}
+		*siglen = _siglen;
+	} while (0);
+
+	EVP_PKEY_CTX_free(ctx);
+	return res;
+}
+
 /*!
  * \brief signs outgoing message with public key
  * \see ast_sign_bin
 */
 int AST_OPTIONAL_API_NAME(ast_sign_bin)(struct ast_key *key, const char *msg, int msglen, unsigned char *dsig)
 {
-	unsigned char digest[20];
-	unsigned int siglen = 128;
+	unsigned char digest[SHA_DIGEST_LENGTH];
+	unsigned digestlen, siglen = 128;
 	int res;
+	EVP_MD_CTX *ctx = NULL;
 
 	if (key->ktype != AST_KEY_PRIVATE) {
 		ast_log(LOG_WARNING, "Cannot sign with a public key\n");
 		return -1;
 	}
 
+	if (siglen < EVP_PKEY_size(key->pkey)) {
+		ast_log(LOG_WARNING, "Signature buffer too small\n");
+		return -1;
+	}
+
 	/* Calculate digest of message */
-	SHA1((unsigned char *)msg, msglen, digest);
+	ctx = EVP_MD_CTX_create();
+	if (ctx == NULL) {
+		ast_log(LOG_ERROR, "Out of memory\n");
+		return -1;
+	}
+	EVP_DigestInit(ctx, EVP_sha1());
+	EVP_DigestUpdate(ctx, msg, msglen);
+	EVP_DigestFinal(ctx, digest, &digestlen);
+	EVP_MD_CTX_destroy(ctx);
 
 	/* Verify signature */
-	if (!(res = RSA_sign(NID_sha1, digest, sizeof(digest), dsig, &siglen, key->rsa))) {
-		ast_log(LOG_WARNING, "RSA Signature (key %s) failed\n", key->name);
+	if ((res = evp_pkey_sign(key->pkey, digest, sizeof(digest), dsig, &siglen, RSA_PKCS1_PADDING)) <= 0) {
+		ast_log(LOG_WARNING, "RSA Signature (key %s) failed %d\n", key->name, res);
 		return -1;
 	}
 
-	if (siglen != 128) {
-		ast_log(LOG_WARNING, "Unexpected signature length %d, expecting %d\n", (int)siglen, (int)128);
+	if (siglen != EVP_PKEY_size(key->pkey)) {
+		ast_log(LOG_WARNING, "Unexpected signature length %u, expecting %d\n", siglen, EVP_PKEY_size(key->pkey));
 		return -1;
 	}
 
 	return 0;
 }
 
+static int evp_pkey_decrypt(EVP_PKEY *pkey, const unsigned char *in, unsigned inlen, unsigned char *out, unsigned *outlen, unsigned padding)
+{
+	EVP_PKEY_CTX *ctx = NULL;
+	int res = -1;
+	size_t _outlen;
+
+	if (*outlen < EVP_PKEY_size(pkey)) {
+		return -1;
+	}
+
+	if (inlen != EVP_PKEY_size(pkey)) {
+		return -1;
+	}
+
+	if ((ctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL) {
+		return -1;
+	}
+
+	do {
+		if ((res = EVP_PKEY_decrypt_init(ctx)) <= 0) {
+			break;
+		}
+		if ((res = EVP_PKEY_CTX_set_rsa_padding(ctx, padding)) <= 0) {
+			break;
+		}
+		_outlen = *outlen;
+		if ((res = EVP_PKEY_decrypt(ctx, out, &_outlen, in, inlen)) <= 0) {
+			break;
+		}
+		res = *outlen = _outlen;
+	} while (0);
+
+	EVP_PKEY_CTX_free(ctx);
+	return res;
+}
+
 /*!
  * \brief decrypt a message
  * \see ast_decrypt_bin
 */
 int AST_OPTIONAL_API_NAME(ast_decrypt_bin)(unsigned char *dst, const unsigned char *src, int srclen, struct ast_key *key)
 {
-	int res, pos = 0;
+	int res;
+	unsigned pos = 0, dstlen, blocksize;
 
 	if (key->ktype != AST_KEY_PRIVATE) {
 		ast_log(LOG_WARNING, "Cannot decrypt with a public key\n");
 		return -1;
 	}
 
-	if (srclen % 128) {
-		ast_log(LOG_NOTICE, "Tried to decrypt something not a multiple of 128 bytes\n");
+	blocksize = EVP_PKEY_size(key->pkey);
+
+	if (srclen % blocksize) {
+		ast_log(LOG_NOTICE, "Tried to decrypt something not a multiple of %u bytes\n", blocksize);
 		return -1;
 	}
 
-	while (srclen) {
+	while (srclen > 0) {
 		/* Process chunks 128 bytes at a time */
-		if ((res = RSA_private_decrypt(128, src, dst, key->rsa, RSA_PKCS1_OAEP_PADDING)) < 0) {
+		dstlen = blocksize;
+		if ((res = evp_pkey_decrypt(key->pkey, src, blocksize, dst, &dstlen, RSA_PKCS1_OAEP_PADDING)) <= 0) {
 			return -1;
 		}
-		pos += res;
-		src += 128;
-		srclen -= 128;
-		dst += res;
+		pos += dstlen;
+		src += blocksize;
+		srclen -= blocksize;
+		dst += dstlen;
 	}
 
 	return pos;
 }
 
+static int evp_pkey_encrypt(EVP_PKEY *pkey, const unsigned char *in, unsigned inlen, unsigned char *out, unsigned *outlen, unsigned padding)
+{
+	EVP_PKEY_CTX *ctx = NULL;
+	int res = -1;
+	size_t _outlen;
+
+	if (padding != RSA_PKCS1_OAEP_PADDING) {
+		ast_log(LOG_WARNING, "Only OAEP padding is supported for now\n");
+		return -1;
+	}
+
+	if (inlen > EVP_PKEY_size(pkey) - RSA_PKCS1_OAEP_PADDING_SIZE) {
+		return -1;
+	}
+
+	if (*outlen < EVP_PKEY_size(pkey)) {
+		return -1;
+	}
+
+	do {
+		if ((ctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL) {
+			break;
+		}
+
+		if ((res = EVP_PKEY_encrypt_init(ctx)) <= 0) {
+			break;
+		}
+		if ((res = EVP_PKEY_CTX_set_rsa_padding(ctx, padding)) <= 0) {
+			break;
+		}
+		_outlen = *outlen;
+		if ((res = EVP_PKEY_encrypt(ctx, out, &_outlen, in, inlen)) <= 0) {
+			break;
+		}
+		res = *outlen = _outlen;
+	} while (0);
+
+	EVP_PKEY_CTX_free(ctx);
+	return res;
+}
+
 /*!
  * \brief encrypt a message
  * \see ast_encrypt_bin
 */
 int AST_OPTIONAL_API_NAME(ast_encrypt_bin)(unsigned char *dst, const unsigned char *src, int srclen, struct ast_key *key)
 {
-	int res, bytes, pos = 0;
+	unsigned bytes, pos = 0, dstlen, blocksize;
+	int res;
 
 	if (key->ktype != AST_KEY_PUBLIC) {
 		ast_log(LOG_WARNING, "Cannot encrypt with a private key\n");
 		return -1;
 	}
 
+	blocksize = EVP_PKEY_size(key->pkey);
+
 	while (srclen) {
 		bytes = srclen;
-		if (bytes > 128 - 41) {
-			bytes = 128 - 41;
+		if (bytes > blocksize - RSA_PKCS1_OAEP_PADDING_SIZE) {
+			bytes = blocksize - RSA_PKCS1_OAEP_PADDING_SIZE;
 		}
 		/* Process chunks 128-41 bytes at a time */
-		if ((res = RSA_public_encrypt(bytes, src, dst, key->rsa, RSA_PKCS1_OAEP_PADDING)) != 128) {
+		dstlen = blocksize;
+		if ((res = evp_pkey_encrypt(key->pkey, src, bytes, dst, &dstlen, RSA_PKCS1_OAEP_PADDING)) != blocksize) {
 			ast_log(LOG_NOTICE, "How odd, encrypted size is %d\n", res);
 			return -1;
 		}
 		src += bytes;
 		srclen -= bytes;
-		pos += res;
-		dst += res;
+		pos += dstlen;
+		dst += dstlen;
 	}
 	return pos;
 }
@@ -398,6 +571,7 @@ int AST_OPTIONAL_API_NAME(ast_encrypt_bin)(unsigned char *dst, const unsigned ch
 */
 int AST_OPTIONAL_API_NAME(ast_sign)(struct ast_key *key, char *msg, char *sig)
 {
+	/* assumes 1024 bit RSA key size */
 	unsigned char dsig[128];
 	int siglen = sizeof(dsig), res;
 
@@ -409,14 +583,48 @@ int AST_OPTIONAL_API_NAME(ast_sign)(struct ast_key *key, char *msg, char *sig)
 	return res;
 }
 
+static int evp_pkey_verify(EVP_PKEY *pkey, const unsigned char *in, unsigned inlen, const unsigned char *sig, unsigned siglen, unsigned padding)
+{
+	EVP_PKEY_CTX *ctx = NULL;
+	int res = -1;
+
+	if (siglen < EVP_PKEY_size(pkey)) {
+		return -1;
+	}
+
+	if ((ctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL) {
+		return -1;
+	}
+
+	do {
+		if ((res = EVP_PKEY_verify_init(ctx)) <= 0) {
+			break;
+		}
+		if ((res = EVP_PKEY_CTX_set_rsa_padding(ctx, padding)) <= 0) {
+			break;
+		}
+		if ((res = EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha1())) <= 0) {
+			break;
+		}
+		if ((res = EVP_PKEY_verify(ctx, sig, siglen, in, inlen)) <= 0) {
+			break;
+		}
+	} while (0);
+
+	EVP_PKEY_CTX_free(ctx);
+	return res;
+}
+
 /*!
  * \brief check signature of a message
  * \see ast_check_signature_bin
 */
 int AST_OPTIONAL_API_NAME(ast_check_signature_bin)(struct ast_key *key, const char *msg, int msglen, const unsigned char *dsig)
 {
-	unsigned char digest[20];
+	unsigned char digest[SHA_DIGEST_LENGTH];
+	unsigned digestlen;
 	int res;
+	EVP_MD_CTX *ctx = NULL;
 
 	if (key->ktype != AST_KEY_PUBLIC) {
 		/* Okay, so of course you really *can* but for our purposes
@@ -426,10 +634,18 @@ int AST_OPTIONAL_API_NAME(ast_check_signature_bin)(struct ast_key *key, const ch
 	}
 
 	/* Calculate digest of message */
-	SHA1((unsigned char *)msg, msglen, digest);
+	ctx = EVP_MD_CTX_create();
+	if (ctx == NULL) {
+		ast_log(LOG_ERROR, "Out of memory\n");
+		return -1;
+	}
+	EVP_DigestInit(ctx, EVP_sha1());
+	EVP_DigestUpdate(ctx, msg, msglen);
+	EVP_DigestFinal(ctx, digest, &digestlen);
+	EVP_MD_CTX_destroy(ctx);
 
 	/* Verify signature */
-	if (!(res = RSA_verify(NID_sha1, digest, sizeof(digest), (unsigned char *)dsig, 128, key->rsa))) {
+	if (!(res = evp_pkey_verify(key->pkey, (const unsigned char *)digest, sizeof(digest), (unsigned char *)dsig, 128, RSA_PKCS1_PADDING))) {
 		ast_debug(1, "Key failed verification: %s\n", key->name);
 		return -1;
 	}
@@ -463,24 +679,124 @@ int AST_OPTIONAL_API_NAME(ast_crypto_loaded)(void)
 	return 1;
 }
 
+int AST_OPTIONAL_API_NAME(ast_crypto_reload)(void)
+{
+	crypto_load(-1, -1);
+	return 1;
+}
+
 int AST_OPTIONAL_API_NAME(ast_aes_set_encrypt_key)(const unsigned char *key, ast_aes_encrypt_key *ctx)
 {
-	return AES_set_encrypt_key(key, 128, ctx);
+	if (key == NULL || ctx == NULL) {
+		return -1;
+	}
+	memcpy(ctx->raw, key, AST_CRYPTO_AES_BLOCKSIZE / 8);
+	return 0;
 }
 
 int AST_OPTIONAL_API_NAME(ast_aes_set_decrypt_key)(const unsigned char *key, ast_aes_decrypt_key *ctx)
 {
-	return AES_set_decrypt_key(key, 128, ctx);
+	if (key == NULL || ctx == NULL) {
+		return -1;
+	}
+	memcpy(ctx->raw, key, AST_CRYPTO_AES_BLOCKSIZE / 8);
+	return 0;
 }
 
-void AST_OPTIONAL_API_NAME(ast_aes_encrypt)(const unsigned char *in, unsigned char *out, const ast_aes_encrypt_key *ctx)
+static int evp_cipher_aes_encrypt(const unsigned char *in, unsigned char *out, unsigned inlen, const ast_aes_encrypt_key *key)
 {
-	return AES_encrypt(in, out, ctx);
+	EVP_CIPHER_CTX *ctx = NULL;
+	int res, outlen, finallen;
+	unsigned char final[AST_CRYPTO_AES_BLOCKSIZE / 8];
+
+	if ((ctx = EVP_CIPHER_CTX_new()) == NULL) {
+		return -1;
+	}
+
+	do {
+		if ((res = EVP_CipherInit(ctx, EVP_aes_128_ecb(), key->raw, NULL, 1)) <= 0) {
+			break;
+		}
+		EVP_CIPHER_CTX_set_padding(ctx, 0);
+		if ((res = EVP_CipherUpdate(ctx, out, &outlen, in, inlen)) <= 0) {
+			break;
+		}
+		/* for ECB, this is a no-op */
+		if ((res = EVP_CipherFinal(ctx, final, &finallen)) <= 0) {
+			break;
+		}
+
+		res = outlen;
+	} while (0);
+
+	EVP_CIPHER_CTX_free(ctx);
+
+	return res;
 }
 
-void AST_OPTIONAL_API_NAME(ast_aes_decrypt)(const unsigned char *in, unsigned char *out, const ast_aes_decrypt_key *ctx)
+int AST_OPTIONAL_API_NAME(ast_aes_encrypt)(const unsigned char *in, unsigned char *out, const ast_aes_encrypt_key *key)
 {
-	return AES_decrypt(in, out, ctx);
+	int res;
+
+	if ((res = evp_cipher_aes_encrypt(in, out, AST_CRYPTO_AES_BLOCKSIZE / 8, key)) <= 0) {
+		ast_log(LOG_ERROR, "AES encryption failed\n");
+	}
+	return res;
+}
+
+static int evp_cipher_aes_decrypt(const unsigned char *in, unsigned char *out, unsigned inlen, const ast_aes_decrypt_key *key)
+{
+	EVP_CIPHER_CTX *ctx = NULL;
+	int res, outlen, finallen;
+	unsigned char final[AST_CRYPTO_AES_BLOCKSIZE / 8];
+
+	if ((ctx = EVP_CIPHER_CTX_new()) == NULL) {
+		return -1;
+	}
+
+	do {
+		if ((res = EVP_CipherInit(ctx, EVP_aes_128_ecb(), key->raw, NULL, 0)) <= 0) {
+			break;
+		}
+		EVP_CIPHER_CTX_set_padding(ctx, 0);
+		if ((res = EVP_CipherUpdate(ctx, out, &outlen, in, inlen)) <= 0) {
+			break;
+		}
+		/* for ECB, this is a no-op */
+		if ((res = EVP_CipherFinal(ctx, final, &finallen)) <= 0) {
+			break;
+		}
+
+		res = outlen;
+	} while (0);
+
+	EVP_CIPHER_CTX_free(ctx);
+
+	return res;
+}
+
+int AST_OPTIONAL_API_NAME(ast_aes_decrypt)(const unsigned char *in, unsigned char *out, const ast_aes_decrypt_key *key)
+{
+	int res;
+
+	if ((res = evp_cipher_aes_decrypt(in, out, AST_CRYPTO_AES_BLOCKSIZE / 8, key)) <= 0) {
+		ast_log(LOG_ERROR, "AES decryption failed\n");
+	}
+	return res;
+}
+
+struct crypto_load_on_file {
+	int ifd;
+	int ofd;
+	int note;
+};
+
+static int crypto_load_cb(const char *directory, const char *file, void *obj)
+{
+	struct crypto_load_on_file *on_file = obj;
+
+	try_load_key(directory, file, on_file->ifd, on_file->ofd, &on_file->note);
+	return 0;
 }
 
 /*!
@@ -491,9 +807,7 @@ void AST_OPTIONAL_API_NAME(ast_aes_decrypt)(const unsigned char *in, unsigned ch
 static void crypto_load(int ifd, int ofd)
 {
 	struct ast_key *key;
-	DIR *dir = NULL;
-	struct dirent *ent;
-	int note = 0;
+	struct crypto_load_on_file on_file = { ifd, ofd, 0 };
 
 	AST_RWLIST_WRLOCK(&keys);
 
@@ -502,17 +816,11 @@ static void crypto_load(int ifd, int ofd)
 		key->delme = 1;
 	}
 
-	/* Load new keys */
-	if ((dir = opendir(ast_config_AST_KEY_DIR))) {
-		while ((ent = readdir(dir))) {
-			try_load_key(ast_config_AST_KEY_DIR, ent->d_name, ifd, ofd, &note);
-		}
-		closedir(dir);
-	} else {
+	if (ast_file_read_dirs(ast_config_AST_KEY_DIR, crypto_load_cb, &on_file, 1) == -1) {
 		ast_log(LOG_WARNING, "Unable to open key directory '%s'\n", ast_config_AST_KEY_DIR);
 	}
 
-	if (note) {
+	if (on_file.note) {
 		ast_log(LOG_NOTICE, "Please run the command 'keys init' to enter the passcodes for the keys\n");
 	}
 
@@ -521,8 +829,8 @@ static void crypto_load(int ifd, int ofd)
 		if (key->delme) {
 			ast_debug(1, "Deleting key %s type %d\n", key->name, key->ktype);
 			AST_RWLIST_REMOVE_CURRENT(list);
-			if (key->rsa) {
-				RSA_free(key->rsa);
+			if (key->pkey) {
+				EVP_PKEY_free(key->pkey);
 			}
 			ast_free(key);
 		}
@@ -535,7 +843,7 @@ static void crypto_load(int ifd, int ofd)
 static void md52sum(char *sum, unsigned char *md5)
 {
 	int x;
-	for (x = 0; x < 16; x++) {
+	for (x = 0; x < MD5_DIGEST_LENGTH; x++) {
 		sum += sprintf(sum, "%02hhx", *(md5++));
 	}
 }
@@ -552,7 +860,7 @@ static char *handle_cli_keys_show(struct ast_cli_entry *e, int cmd, struct ast_c
 #define FORMAT "%-18s %-8s %-16s %-33s\n"
 
 	struct ast_key *key;
-	char sum[16 * 2 + 1];
+	char sum[MD5_DIGEST_LENGTH * 2 + 1];
 	int count_keys = 0;
 
 	switch (cmd) {
diff --git a/res/res_fax.c b/res/res_fax.c
index be7f5092a52c874c197ddb4923dfddb30402ff9a..18d7ab8f355ac4fe9b3348efe24c3f97e590741c 100644
--- a/res/res_fax.c
+++ b/res/res_fax.c
@@ -35,7 +35,6 @@
  */
 
 /*** MODULEINFO
-	<conflict>app_fax</conflict>
 	<support_level>core</support_level>
 ***/
 
diff --git a/res/res_geolocation.c b/res/res_geolocation.c
new file mode 100644
index 0000000000000000000000000000000000000000..19dd84b0d5f18d633a056d8eeea47486d2f4d0dd
--- /dev/null
+++ b/res/res_geolocation.c
@@ -0,0 +1,125 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>libxml2</depend>
+	<depend>libxslt</depend>
+	<support_level>core</support_level>
+ ***/
+
+
+#include "asterisk.h"
+#define AST_API_MODULE
+#include "asterisk/res_geolocation.h"
+#include "res_geolocation/geoloc_private.h"
+
+static int reload_module(void)
+{
+	int res = 0;
+
+	res = geoloc_civicaddr_reload();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	res = geoloc_gml_reload();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	res = geoloc_config_reload();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	res = geoloc_eprofile_reload();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	res = geoloc_dialplan_reload();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	res = geoloc_channel_reload();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	int res = 0;
+
+	res += geoloc_channel_unload();
+	res += geoloc_dialplan_unload();
+	res += geoloc_eprofile_unload();
+	res += geoloc_config_unload();
+	res += geoloc_gml_unload();
+	res += geoloc_civicaddr_unload();
+
+	return (res != 0);
+}
+
+static int load_module(void)
+{
+	int res = 0;
+
+	res = geoloc_civicaddr_load();
+	if (res) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	res = geoloc_gml_load();
+	if (res) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	res = geoloc_config_load();
+	if (res) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	res = geoloc_eprofile_load();
+	if (res) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	res = geoloc_dialplan_load();
+	if (res) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	res = geoloc_channel_load();
+	if (res) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "res_geolocation Module for Asterisk",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.reload = reload_module,
+	.load_pri = AST_MODPRI_CHANNEL_DEPEND - 10,
+);
diff --git a/res/res_geolocation.exports.in b/res/res_geolocation.exports.in
new file mode 100644
index 0000000000000000000000000000000000000000..da0a9810aaa81f34cbc2d1766d982f87415c04bb
--- /dev/null
+++ b/res/res_geolocation.exports.in
@@ -0,0 +1,6 @@
+{
+	global:
+		LINKER_SYMBOL_PREFIXast_geo*;
+	local:
+		*;
+};
diff --git a/res/res_geolocation/eprofile_to_pidf.xslt b/res/res_geolocation/eprofile_to_pidf.xslt
new file mode 100644
index 0000000000000000000000000000000000000000..797fa66cdf97ae4644f9ae22f2b00e5a3060546e
--- /dev/null
+++ b/res/res_geolocation/eprofile_to_pidf.xslt
@@ -0,0 +1,235 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.1"
+	xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
+	xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
+	xmlns:fn="http://www.w3.org/2005/xpath-functions"
+	xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
+	xmlns:gml="http://www.opengis.net/gml"
+	xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
+	xmlns:gs="http://www.opengis.net/pidflo/1.0"
+	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+	xmlns:con="urn:ietf:params:xml:ns:geopriv:conf"
+	xmlns:date="http://exslt.org/dates-and-times">
+
+	<xsl:output method="xml" indent="yes"/>
+	<xsl:strip-space elements="*"/>
+	<xsl:param name="suppress_empty_ca_elements" select="false()"/>
+
+	<!-- REMINDER:  The "match" and "select" xpaths refer to the input document,
+		not the output document -->
+
+	<xsl:template match="presence">
+		<!-- xslt will take care of adding all of the namespace declarations
+			from the list above -->
+		<presence xmlns="urn:ietf:params:xml:ns:pidf" entity="{@entity}">
+			<xsl:apply-templates/>
+		</presence>
+	</xsl:template>
+
+	<xsl:template match="person|device">
+		<xsl:element name="dm:{local-name(.)}">
+			<xsl:if test="@id">
+				<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
+			</xsl:if>
+
+			<gp:geopriv>
+				<xsl:apply-templates select="./location-info"/>
+				<xsl:apply-templates select="./usage-rules"/>
+				<xsl:apply-templates select="./method"/>
+				<xsl:apply-templates select="./note-well"/>
+			</gp:geopriv>
+			<xsl:if test="./timestamp">
+				<dm:timestamp>
+					<xsl:value-of select="./timestamp"/>
+				</dm:timestamp>
+			</xsl:if>
+			<xsl:if test="./deviceID">
+				<dm:deviceID>
+					<xsl:value-of select="./deviceID"/>
+				</dm:deviceID>
+			</xsl:if>
+		</xsl:element>
+	</xsl:template>
+
+	<xsl:template match="tuple">
+		<xsl:element name="tuple" namespace="urn:ietf:params:xml:ns:pidf">
+			<xsl:element name="status" namespace="urn:ietf:params:xml:ns:pidf">
+				<gp:geopriv>
+					<xsl:apply-templates select="./location-info"/>
+					<xsl:apply-templates select="./usage-rules"/>
+					<xsl:apply-templates select="./method"/>
+					<xsl:apply-templates select="./note-well"/>
+				</gp:geopriv>
+			</xsl:element>
+			<xsl:if test="./timestamp">
+				<xsl:element name="timestamp" namespace="urn:ietf:params:xml:ns:pidf">
+					<xsl:value-of select="./timestamp"/>
+				</xsl:element>
+			</xsl:if>
+		</xsl:element>
+	</xsl:template>
+
+
+	<xsl:template match="location-info">
+		<gp:location-info>
+			<xsl:apply-templates/>
+		</gp:location-info>
+	</xsl:template>
+
+	<!-- When we're using the civicAddress format, the translation is simple.
+		We add gp:location-info and ca:civicAddress, then we just copy in
+		each element, adding the "ca" namespace -->
+
+	<xsl:template match="civicAddress/*">
+		<xsl:if test="not($suppress_empty_ca_elements) or boolean(node())">
+			<xsl:element name="ca:{name()}">
+				<xsl:value-of select="."/>
+			</xsl:element>
+		</xsl:if>
+	</xsl:template>
+
+	<xsl:template match="location-info/civicAddress">
+		<ca:civicAddress xml:lang="{@lang}">
+			<xsl:apply-templates/>
+		</ca:civicAddress>
+	</xsl:template>
+
+	<!-- All GML shapes share common processing for the "srsName" attribute -->
+	<xsl:template name="shape">
+		<xsl:choose>
+			<xsl:when test="@crs = '3d'">
+				<xsl:attribute name="srsName">urn:ogc:def:crs:EPSG::4979</xsl:attribute>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:attribute name="srsName">urn:ogc:def:crs:EPSG::4326</xsl:attribute>
+			</xsl:otherwise>
+		</xsl:choose>
+	</xsl:template>
+
+	<!-- The GML shapes themselves.  They don't all have the same namespace unfortunately... -->
+
+	<xsl:template match="Point|Circle|Ellipse|ArcBand|Sphere|Ellipsoid">
+		<xsl:variable name="namespace">
+			<xsl:choose>
+				<xsl:when test="name() = 'Point'">
+					<xsl:value-of select="'gml'"/>
+				</xsl:when>
+				<xsl:otherwise>
+					<xsl:value-of select="'gs'"/>
+				</xsl:otherwise>
+			</xsl:choose>
+		</xsl:variable>
+
+		<xsl:element name="{$namespace}:{name()}">
+			<xsl:call-template name="shape"/>
+			<xsl:apply-templates select="./*"/>
+		</xsl:element>
+	</xsl:template>
+
+	<!-- ... and some are more complex than others. -->
+
+	<xsl:template match="Polygon">
+		<gml:Polygon>
+			<xsl:call-template name="shape"/>
+			<gml:exterior>
+				<gml:LinearRing>
+					<xsl:apply-templates select="./pos|posList"/>
+				</gml:LinearRing>
+			</gml:exterior>
+		</gml:Polygon>
+	</xsl:template>
+
+	<!-- Prism with a Polygon and height -->
+	<xsl:template match="Prism">
+		<gs:Prism>
+			<xsl:call-template name="shape"/>
+			<gs:base>
+				<gml:Polygon>
+					<gml:exterior>
+						<gml:LinearRing>
+							<xsl:apply-templates select="./pos|posList"/>
+						</gml:LinearRing>
+					</gml:exterior>
+				</gml:Polygon>
+			</gs:base>
+			<xsl:apply-templates select="./height"/>
+		</gs:Prism>
+	</xsl:template>
+
+	<!-- method has no children so we add the "gp" namespace and copy in the value -->
+	<xsl:template match="method">
+		<gp:method>
+			 <xsl:value-of select="."/>
+		 </gp:method>
+	</xsl:template>
+
+	<!-- note-well has no children so we add the "gp" namespace and copy in the value -->
+	<xsl:template match="note-well">
+		<gp:note-well>
+			 <xsl:value-of select="."/>
+		 </gp:note-well>
+	</xsl:template>
+
+	<!-- usage-rules does have children so we add the "gp" namespace and copy in
+		the children, also adding the "gp" namespace -->
+	<xsl:template match="usage-rules">
+		<gp:usage-rules>
+			 <xsl:for-each select="*">
+				 <xsl:element name="gp:{local-name()}">
+					 <xsl:value-of select="."/>
+				 </xsl:element>
+			 </xsl:for-each>
+		</gp:usage-rules>
+	</xsl:template>
+
+	<!-- These are the GML format primitives -->
+
+	<xsl:template name="name-value">
+		<xsl:element name="gml:{name()}">
+			<xsl:value-of select="."/>
+		</xsl:element>
+	</xsl:template>
+
+	<xsl:template name="length">
+		<xsl:element name="gs:{name()}">
+			<xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9001</xsl:attribute>
+			<xsl:value-of select="."/>
+		</xsl:element>
+	</xsl:template>
+
+	<xsl:template name="angle">
+		<xsl:element name="gs:{name()}">
+			<xsl:choose>
+				<xsl:when test="@uom = 'radians'">
+					<xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9102</xsl:attribute>
+				</xsl:when>
+				<xsl:otherwise>
+					<xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9101</xsl:attribute>
+				</xsl:otherwise>
+			</xsl:choose>
+			<xsl:value-of select="."/>
+		</xsl:element>
+	</xsl:template>
+
+	<!-- These are the GML shape parameters -->
+
+	<xsl:template match="orientation"><xsl:call-template name="angle" /></xsl:template>
+	<xsl:template match="radius"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="height"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="semiMajorAxis"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="semiMinorAxis"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="verticalAxis"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="innerRadius"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="outerRadius"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="startAngle"><xsl:call-template name="angle" /></xsl:template>
+	<xsl:template match="openingAngle"><xsl:call-template name="angle" /></xsl:template>
+	<xsl:template match="pos"><xsl:call-template name="name-value" /></xsl:template>
+	<xsl:template match="posList"><xsl:call-template name="name-value" /></xsl:template>
+
+	<xsl:template match="confidence">
+		<con:confidence pdf="{@pdf}">
+			<xsl:value-of select="."/>
+		</con:confidence>
+	</xsl:template>
+
+</xsl:stylesheet>
diff --git a/res/res_geolocation/geoloc_civicaddr.c b/res/res_geolocation/geoloc_civicaddr.c
new file mode 100644
index 0000000000000000000000000000000000000000..f5a7c22c5d7dcaf5e1246ae2301134faa33d01ed
--- /dev/null
+++ b/res/res_geolocation/geoloc_civicaddr.c
@@ -0,0 +1,151 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "asterisk/config.h"
+#include "asterisk/cli.h"
+#include "asterisk/res_geolocation.h"
+#include "asterisk/xml.h"
+#include "geoloc_private.h"
+
+static const char *addr_code_name_entries[] = {
+	"country",
+	"A1",
+	"A2",
+	"A3",
+	"A4",
+	"A5",
+	"A6",
+	"ADDCODE",
+	"BLD",
+	"FLR",
+	"HNO",
+	"HNS",
+	"LMK",
+	"LOC",
+	"NAM",
+	"PC",
+	"PCN",
+	"PLC",
+	"POBOX",
+	"POD",
+	"POM",
+	"PRD",
+	"PRM",
+	"RD",
+	"RD",
+	"RDBR",
+	"RDSEC",
+	"RDSUBBR",
+	"ROOM",
+	"SEAT",
+	"STS",
+	"UNIT",
+};
+
+static int compare_civicaddr_codes(const void *_a, const void *_b)
+{
+	/* See the man page for qsort(3) for an explanation of the casts */
+	int rc = strcmp(*(const char **)_a, *(const char **)_b);
+	return rc;
+}
+
+int ast_geoloc_civicaddr_is_code_valid(const char *code)
+{
+	const char **entry = bsearch(&code, addr_code_name_entries, ARRAY_LEN(addr_code_name_entries),
+		sizeof(const char *), compare_civicaddr_codes);
+	return (entry != NULL);
+}
+
+enum ast_geoloc_validate_result ast_geoloc_civicaddr_validate_varlist(
+	const struct ast_variable *varlist,	const char **result)
+{
+	const struct ast_variable *var = varlist;
+	for (; var; var = var->next) {
+		int valid = ast_geoloc_civicaddr_is_code_valid(var->name);
+		if (!valid) {
+			*result = var->name;
+			return AST_GEOLOC_VALIDATE_INVALID_VARNAME;
+		}
+	}
+	return AST_GEOLOC_VALIDATE_SUCCESS;
+}
+
+struct ast_xml_node *geoloc_civicaddr_list_to_xml(const struct ast_variable *resolved_location,
+	const char *ref_string)
+{
+	char *lang = NULL;
+	char *s = NULL;
+	struct ast_variable *var;
+	struct ast_xml_node *ca_node;
+	struct ast_xml_node *child_node;
+	int rc = 0;
+	SCOPE_ENTER(3, "%s", ref_string);
+
+	lang = (char *)ast_variable_find_in_list(resolved_location, "lang");
+	if (ast_strlen_zero(lang)) {
+		lang = ast_strdupa(ast_defaultlanguage);
+		for (s = lang; *s; s++) {
+			if (*s == '_') {
+				*s = '-';
+			}
+		}
+	}
+
+	ca_node = ast_xml_new_node("civicAddress");
+	if (!ca_node) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'civicAddress' XML node\n", ref_string);
+	}
+	rc = ast_xml_set_attribute(ca_node, "lang", lang);
+	if (rc != 0) {
+		ast_xml_free_node(ca_node);
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'lang' XML attribute\n", ref_string);
+	}
+
+	for (var = (struct ast_variable *)resolved_location; var; var = var->next) {
+		if (ast_strings_equal(var->name, "lang")) {
+			continue;
+		}
+		child_node = ast_xml_new_child(ca_node, var->name);
+		if (!child_node) {
+			ast_xml_free_node(ca_node);
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create '%s' XML node\n", var->name, ref_string);
+		}
+		ast_xml_set_text(child_node, var->value);
+	}
+
+	SCOPE_EXIT_RTN_VALUE(ca_node, "%s: Done\n", ref_string);
+}
+
+int geoloc_civicaddr_unload(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_civicaddr_load(void)
+{
+	qsort(addr_code_name_entries, ARRAY_LEN(addr_code_name_entries), sizeof(const char *),
+		compare_civicaddr_codes);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_civicaddr_reload(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
diff --git a/res/res_geolocation/geoloc_common.c b/res/res_geolocation/geoloc_common.c
new file mode 100644
index 0000000000000000000000000000000000000000..bb24a3100ad27b87e0e8c4b0449eea8fe9b9caed
--- /dev/null
+++ b/res/res_geolocation/geoloc_common.c
@@ -0,0 +1,36 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "geoloc_private.h"
+
+static const char *result_names[] = {
+	"Success",
+	"Missing type",
+	"Invalid shape type",
+	"Invalid variable name",
+	"Not enough variables",
+	"Too many variables",
+	"Invalid variable value"
+};
+
+const char *ast_geoloc_validate_result_to_str(enum ast_geoloc_validate_result result)
+{
+	return result_names[result];
+}
+
diff --git a/res/res_geolocation/geoloc_config.c b/res/res_geolocation/geoloc_config.c
new file mode 100644
index 0000000000000000000000000000000000000000..26dd2d9fd1205c9a467e1f85b9ec2c442070e990
--- /dev/null
+++ b/res/res_geolocation/geoloc_config.c
@@ -0,0 +1,761 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#define AST_API_MODULE
+#include "geoloc_private.h"
+
+static struct ast_sorcery *geoloc_sorcery;
+
+static const char *pidf_element_names[] = {
+	"<none>",
+	"device",
+	"tuple",
+	"person"
+};
+
+static const char *format_names[] = {
+	"<none>",
+	"civicAddress",
+	"GML",
+	"URI",
+};
+
+static const char * precedence_names[] = {
+	"prefer_incoming",
+	"prefer_config",
+	"discard_incoming",
+	"discard_config",
+};
+
+CONFIG_ENUM(location, format)
+CONFIG_VAR_LIST(location, location_info)
+CONFIG_VAR_LIST(location, confidence)
+
+static void geoloc_location_destructor(void *obj) {
+	struct ast_geoloc_location *location = obj;
+
+	ast_string_field_free_memory(location);
+	ast_variables_destroy(location->location_info);
+	ast_variables_destroy(location->confidence);
+}
+
+static void *geoloc_location_alloc(const char *name)
+{
+	struct ast_geoloc_location *location = ast_sorcery_generic_alloc(sizeof(struct ast_geoloc_location), geoloc_location_destructor);
+	if (location) {
+		ast_string_field_init(location, 128);
+	}
+
+	return location;
+}
+
+CONFIG_ENUM(profile, pidf_element)
+CONFIG_ENUM(profile, precedence)
+CONFIG_VAR_LIST(profile, location_refinement)
+CONFIG_VAR_LIST(profile, location_variables)
+CONFIG_VAR_LIST(profile, usage_rules)
+
+CONFIG_ENUM_HANDLER(profile, format)
+CONFIG_ENUM_TO_STR(profile, format)
+CONFIG_VAR_LIST(profile, location_info)
+CONFIG_VAR_LIST(profile, confidence)
+
+static void geoloc_profile_destructor(void *obj) {
+	struct ast_geoloc_profile *profile = obj;
+
+	ast_string_field_free_memory(profile);
+	ast_variables_destroy(profile->location_refinement);
+	ast_variables_destroy(profile->location_variables);
+	ast_variables_destroy(profile->usage_rules);
+	ast_variables_destroy(profile->location_info);
+	ast_variables_destroy(profile->confidence);
+}
+
+static void *geoloc_profile_alloc(const char *name)
+{
+	struct ast_geoloc_profile *profile = ast_sorcery_generic_alloc(sizeof(*profile),
+		geoloc_profile_destructor);
+	if (profile) {
+		ast_string_field_init(profile, 128);
+	}
+
+	return profile;
+}
+
+static enum ast_geoloc_validate_result validate_location_info(const char *id,
+	enum ast_geoloc_format format, struct ast_variable *location_info)
+{
+	enum ast_geoloc_validate_result result;
+	const char *failed;
+	const char *uri;
+
+	switch (format) {
+	case AST_GEOLOC_FORMAT_NONE:
+	case AST_GEOLOC_FORMAT_LAST:
+		ast_log(LOG_ERROR, "Location '%s' must have a format\n", id);
+		return -1;
+	case AST_GEOLOC_FORMAT_CIVIC_ADDRESS:
+		result = ast_geoloc_civicaddr_validate_varlist(location_info, &failed);
+		if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
+			ast_log(LOG_ERROR, "Location '%s' has invalid item '%s' in the location\n",
+				id, failed);
+			return result;
+		}
+		break;
+	case AST_GEOLOC_FORMAT_GML:
+		result = ast_geoloc_gml_validate_varlist(location_info, &failed);
+		if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
+			ast_log(LOG_ERROR, "%s for item '%s' in location '%s'\n",
+				ast_geoloc_validate_result_to_str(result),	failed, id);
+			return result;
+		}
+
+		break;
+	case AST_GEOLOC_FORMAT_URI:
+		uri = ast_variable_find_in_list(location_info, "URI");
+		if (!uri) {
+			struct ast_str *str = ast_variable_list_join(location_info, ",", "=", "\"", NULL);
+
+			ast_log(LOG_ERROR, "Geolocation location '%s' format is set to '%s' but no 'URI' was found in location parameter '%s'\n",
+				id, format_names[AST_GEOLOC_FORMAT_URI], ast_str_buffer(str));
+			ast_free(str);
+			return AST_GEOLOC_VALIDATE_NOT_ENOUGH_VARNAMES;
+		}
+		break;
+	}
+
+	return AST_GEOLOC_VALIDATE_SUCCESS;
+}
+
+static int validate_location_source(const char *id, const char *location_source)
+{
+	if (!ast_strlen_zero(location_source)) {
+		struct ast_sockaddr loc_source_addr;
+		int rc = ast_sockaddr_parse(&loc_source_addr, location_source, PARSE_PORT_FORBID);
+		if (rc == 1) {
+			ast_log(LOG_ERROR, "Geolocation location '%s' location_source '%s' must be a FQDN."
+				" RFC8787 expressly forbids IP addresses.\n",
+				id, location_source);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int geoloc_location_apply_handler(const struct ast_sorcery *sorcery, void *obj)
+{
+	struct ast_geoloc_location *location = obj;
+	const char *location_id = ast_sorcery_object_get_id(location);
+	enum ast_geoloc_validate_result result;
+	int rc = 0;
+
+	result = validate_location_info(location_id, location->format, location->location_info);
+	if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
+		return -1;
+	}
+
+	rc = validate_location_source(location_id, location->location_source);
+	if (rc != 0) {
+		return -1;
+	}
+
+	return 0;
+}
+
+static int geoloc_profile_apply_handler(const struct ast_sorcery *sorcery, void *obj)
+{
+	struct ast_geoloc_profile *profile = obj;
+	struct ast_geoloc_location *location;
+	const char *id = ast_sorcery_object_get_id(profile);
+	enum ast_geoloc_validate_result result;
+	enum ast_geoloc_format format = AST_GEOLOC_FORMAT_NONE;
+	int rc = 0;
+
+	if (!ast_strlen_zero(profile->location_reference)) {
+		if (profile->location_info ||
+			profile->format != AST_GEOLOC_FORMAT_NONE) {
+			ast_log(LOG_ERROR, "Profile '%s' can't have location_reference and location_info or format at the same time",
+				id);
+			return -1;
+		}
+		return 0;
+	}
+
+	if (profile->location_info) {
+		result = validate_location_info(id, profile->format, profile->location_info);
+		if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
+			return -1;
+		}
+
+		rc = validate_location_source(id, profile->location_source);
+		if (rc != 0) {
+			return -1;
+		}
+
+		return 0;
+	}
+
+	if (!ast_strlen_zero(profile->location_reference)) {
+		location = ast_sorcery_retrieve_by_id(geoloc_sorcery, "location", profile->location_reference);
+		if (!location) {
+			ast_log(LOG_ERROR, "Profile '%s' has a location_reference '%s' that doesn't exist",
+				id, profile->location_reference);
+			return -1;
+		}
+		format = location->format;
+		ao2_ref(location, -1);
+	}
+
+	if (profile->location_refinement) {
+		result = validate_location_info(id, format, profile->location_refinement);
+		if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+struct ast_sorcery *geoloc_get_sorcery(void)
+{
+	ast_sorcery_ref(geoloc_sorcery);
+	return geoloc_sorcery;
+}
+
+static char *geoloc_config_list_locations(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ao2_iterator iter;
+	struct ao2_container *sorted_container;
+	struct ao2_container *unsorted_container;
+	struct ast_geoloc_location *loc;
+	int using_regex = 0;
+	char *result = CLI_SUCCESS;
+	int ret = 0;
+	int count = 0;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "geoloc list locations";
+		e->usage = "Usage: geoloc list locations [ like <pattern> ]\n"
+		            "      List Geolocation Location Objects\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 3 && a->argc != 5) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (a->argc == 5) {
+		if (strcasecmp(a->argv[3], "like")) {
+			return CLI_SHOWUSAGE;
+		}
+		using_regex = 1;
+	}
+
+	sorted_container = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
+		ast_sorcery_object_id_sort, NULL);
+	if (!sorted_container) {
+		ast_cli(a->fd, "Geolocation Location Objects: Unable to allocate temporary container\n");
+		return CLI_FAILURE;
+	}
+
+	/* Get a sorted snapshot of the scheduled tasks */
+	if (using_regex) {
+		unsorted_container = ast_sorcery_retrieve_by_regex(geoloc_sorcery, "location", a->argv[4]);
+	} else {
+		unsorted_container = ast_sorcery_retrieve_by_fields(geoloc_sorcery, "location",
+			AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+	}
+
+	ret = ao2_container_dup(sorted_container, unsorted_container, 0);
+	ao2_ref(unsorted_container, -1);
+	if (ret != 0) {
+		ao2_ref(sorted_container, -1);
+		ast_cli(a->fd, "Geolocation Location Objects: Unable to sort temporary container\n");
+		return CLI_FAILURE;
+	}
+
+	ast_cli(a->fd, "Geolocation Location Objects:\n\n");
+
+	ast_cli(a->fd,
+		"<Object ID...................................> <Format.....> <Details.............>\n"
+		"===================================================================================\n");
+
+	iter = ao2_iterator_init(sorted_container, AO2_ITERATOR_UNLINK);
+	for (; (loc = ao2_iterator_next(&iter)); ao2_ref(loc, -1)) {
+		struct ast_str *str;
+
+		ao2_lock(loc);
+		str = ast_variable_list_join(loc->location_info, ",", "=", "\"", NULL);
+		if (!str) {
+			ao2_unlock(loc);
+			ao2_ref(loc, -1);
+			ast_cli(a->fd, "Geolocation Location Objects: Unable to allocate temp string for '%s'\n",
+				ast_sorcery_object_get_id(loc));
+			result = CLI_FAILURE;
+			break;
+		}
+
+		ast_cli(a->fd, "%-46.46s %-13s %-s\n",
+			ast_sorcery_object_get_id(loc),
+			format_names[loc->format],
+			ast_str_buffer(str));
+		ao2_unlock(loc);
+		ast_free(str);
+		count++;
+	}
+	ao2_iterator_destroy(&iter);
+	ao2_ref(sorted_container, -1);
+	ast_cli(a->fd, "\nTotal Location Objects: %d\n\n", count);
+
+	return result;
+}
+
+static char *geoloc_config_list_profiles(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ao2_iterator iter;
+	struct ao2_container *sorted_container;
+	struct ao2_container *unsorted_container;
+	struct ast_geoloc_profile *profile;
+	int using_regex = 0;
+	char *result = CLI_SUCCESS;
+	int ret = 0;
+	int count = 0;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "geoloc list profiles";
+		e->usage = "Usage: geoloc list profiles [ like <pattern> ]\n"
+		            "      List Geolocation Profile Objects\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 3 && a->argc != 5) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (a->argc == 5) {
+		if (strcasecmp(a->argv[3], "like")) {
+			return CLI_SHOWUSAGE;
+		}
+		using_regex = 1;
+	}
+
+	sorted_container = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
+		ast_sorcery_object_id_sort, NULL);
+	if (!sorted_container) {
+		ast_cli(a->fd, "Geolocation Profile Objects: Unable to allocate temporary container\n");
+		return CLI_FAILURE;
+	}
+
+	/* Get a sorted snapshot of the scheduled tasks */
+	if (using_regex) {
+		unsorted_container = ast_sorcery_retrieve_by_regex(geoloc_sorcery, "profile", a->argv[4]);
+	} else {
+		unsorted_container = ast_sorcery_retrieve_by_fields(geoloc_sorcery, "profile",
+			AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+	}
+
+	ret = ao2_container_dup(sorted_container, unsorted_container, 0);
+	ao2_ref(unsorted_container, -1);
+	if (ret != 0) {
+		ao2_ref(sorted_container, -1);
+		ast_cli(a->fd, "Geolocation Profile Objects: Unable to sort temporary container\n");
+		return CLI_FAILURE;
+	}
+
+	ast_cli(a->fd, "Geolocation Profile Objects:\n\n");
+
+	ast_cli(a->fd,
+		"<Object ID...................................> <Profile Action> <Location Reference> \n"
+		"=====================================================================================\n");
+
+	iter = ao2_iterator_init(sorted_container, AO2_ITERATOR_UNLINK);
+	for (; (profile = ao2_iterator_next(&iter)); ao2_ref(profile, -1)) {
+		ao2_lock(profile);
+
+		ast_cli(a->fd, "%-46.46s %-16s %-s\n",
+			ast_sorcery_object_get_id(profile),
+			precedence_names[profile->precedence],
+			profile->location_reference);
+		ao2_unlock(profile);
+		count++;
+	}
+	ao2_iterator_destroy(&iter);
+	ao2_ref(sorted_container, -1);
+	ast_cli(a->fd, "\nTotal Profile Objects: %d\n\n", count);
+
+	return result;
+}
+
+static char *geoloc_config_show_profiles(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ao2_iterator iter;
+	struct ao2_container *sorted_container;
+	struct ao2_container *unsorted_container;
+	struct ast_geoloc_profile *profile;
+	int using_regex = 0;
+	char *result = CLI_SUCCESS;
+	int ret = 0;
+	int count = 0;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "geoloc show profiles";
+		e->usage = "Usage: geoloc show profiles [ like <pattern> ]\n"
+		            "      List Geolocation Profile Objects\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 3 && a->argc != 5) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (a->argc == 5) {
+		if (strcasecmp(a->argv[3], "like")) {
+			return CLI_SHOWUSAGE;
+		}
+		using_regex = 1;
+	}
+
+	/* Create an empty rb-tree container which always sorts its contents. */
+	sorted_container = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
+		ast_sorcery_object_id_sort, NULL);
+	if (!sorted_container) {
+		ast_cli(a->fd, "Geolocation Profile Objects: Unable to allocate temporary container\n");
+		return CLI_FAILURE;
+	}
+
+	/* Get an unsorted list of profile parameters */
+	if (using_regex) {
+		unsorted_container = ast_sorcery_retrieve_by_regex(geoloc_sorcery, "profile", a->argv[4]);
+	} else {
+		unsorted_container = ast_sorcery_retrieve_by_fields(geoloc_sorcery, "profile",
+			AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+	}
+
+	/* Copy the unsorted parameters into the rb-tree container which will sort them automatically. */
+	ret = ao2_container_dup(sorted_container, unsorted_container, 0);
+	ao2_ref(unsorted_container, -1);
+	if (ret != 0) {
+		ao2_ref(sorted_container, -1);
+		ast_cli(a->fd, "Geolocation Profile Objects: Unable to sort temporary container\n");
+		return CLI_FAILURE;
+	}
+
+	ast_cli(a->fd, "Geolocation Profile Objects:\n");
+
+	iter = ao2_iterator_init(sorted_container, AO2_ITERATOR_UNLINK);
+	for (; (profile = ao2_iterator_next(&iter)); ) {
+		struct ast_str *loc_str = NULL;
+		struct ast_str *refinement_str = NULL;
+		struct ast_str *variables_str = NULL;
+		struct ast_str *resolved_str = NULL;
+		struct ast_str *usage_rules_str = NULL;
+		struct ast_str *confidence_str = NULL;
+		struct ast_geoloc_eprofile *eprofile = ast_geoloc_eprofile_create_from_profile(profile);
+		ao2_ref(profile, -1);
+
+		loc_str = ast_variable_list_join(eprofile->location_info, ",", "=", "\"", NULL);
+		resolved_str = ast_variable_list_join(eprofile->effective_location, ",", "=", "\"", NULL);
+
+		refinement_str = ast_variable_list_join(eprofile->location_refinement, ",", "=", "\"", NULL);
+		variables_str = ast_variable_list_join(eprofile->location_variables, ",", "=", "\"", NULL);
+		usage_rules_str = ast_variable_list_join(eprofile->usage_rules, ",", "=", "\"", NULL);
+		confidence_str = ast_variable_list_join(eprofile->confidence, ",", "=", "\"", NULL);
+
+		ast_cli(a->fd,"\n"
+			"id:                      %-s\n"
+			"profile_precedence:      %-s\n"
+			"pidf_element:            %-s\n"
+			"location_reference:      %-s\n"
+			"location_format:         %-s\n"
+			"location_info:           %-s\n"
+			"location_method:         %-s\n"
+			"location_source:         %-s\n"
+			"location_confidence:     %-s\n"
+			"location_refinement:     %-s\n"
+			"location_variables:      %-s\n"
+			"allow_routing_use:       %-s\n"
+			"suppress_empty_elements: %-s\n"
+			"effective_location:      %-s\n"
+			"usage_rules:             %-s\n"
+			"notes:                   %-s\n",
+			eprofile->id,
+			precedence_names[eprofile->precedence],
+			pidf_element_names[eprofile->pidf_element],
+			S_OR(eprofile->location_reference, "<none>"),
+			format_names[eprofile->format],
+			S_COR(loc_str, ast_str_buffer(loc_str), "<none>"),
+			S_OR(eprofile->method, "<none>"),
+			S_OR(eprofile->location_source, "<none>"),
+			S_COR(confidence_str, ast_str_buffer(confidence_str), "<none>"),
+			S_COR(refinement_str, ast_str_buffer(refinement_str), "<none>"),
+			S_COR(variables_str, ast_str_buffer(variables_str), "<none>"),
+			S_COR(eprofile->allow_routing_use, "yes", "no"),
+			S_COR(eprofile->suppress_empty_ca_elements, "yes", "no"),
+			S_COR(resolved_str, ast_str_buffer(resolved_str), "<none>"),
+			S_COR(usage_rules_str, ast_str_buffer(usage_rules_str), "<none>"),
+			S_OR(eprofile->notes, "<none>")
+			);
+		ao2_ref(eprofile, -1);
+
+		ast_free(loc_str);
+		ast_free(refinement_str);
+		ast_free(variables_str);
+		ast_free(resolved_str);
+		ast_free(usage_rules_str);
+		ast_free(confidence_str);
+		count++;
+	}
+	ao2_iterator_destroy(&iter);
+	ao2_ref(sorted_container, -1);
+	ast_cli(a->fd, "\nTotal Profile Objects: %d\n\n", count);
+
+	return result;
+}
+
+static char *geoloc_config_cli_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	char *result = CLI_SUCCESS;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "geoloc reload";
+		e->usage = "Usage: geoloc reload\n"
+		            "      Reload Geolocation Configuration\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 2) {
+		return CLI_SHOWUSAGE;
+	}
+
+	geoloc_config_reload();
+	ast_cli(a->fd, "Geolocation Configuration reloaded.\n");
+
+	return result;
+}
+
+static struct ast_cli_entry geoloc_location_cli_commands[] = {
+	AST_CLI_DEFINE(geoloc_config_list_locations, "List Geolocation Location Objects"),
+	AST_CLI_DEFINE(geoloc_config_list_profiles, "List Geolocation Profile Objects"),
+	AST_CLI_DEFINE(geoloc_config_show_profiles, "Show Geolocation Profile Objects"),
+	AST_CLI_DEFINE(geoloc_config_cli_reload, "Reload Geolocation Configuration"),
+};
+
+struct ast_geoloc_location * AST_OPTIONAL_API_NAME(ast_geoloc_get_location)(const char *id)
+{
+	if (ast_strlen_zero(id)) {
+		return NULL;
+	}
+
+	return ast_sorcery_retrieve_by_id(geoloc_sorcery, "location", id);
+}
+
+struct ast_geoloc_profile * AST_OPTIONAL_API_NAME(ast_geoloc_get_profile)(const char *id)
+{
+	if (ast_strlen_zero(id)) {
+		return NULL;
+	}
+
+	return ast_sorcery_retrieve_by_id(geoloc_sorcery, "profile", id);
+}
+
+int geoloc_config_reload(void)
+{
+	if (geoloc_sorcery) {
+		ast_sorcery_reload(geoloc_sorcery);
+	}
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_config_unload(void)
+{
+	ast_cli_unregister_multiple(geoloc_location_cli_commands, ARRAY_LEN(geoloc_location_cli_commands));
+
+	ast_sorcery_object_unregister(geoloc_sorcery, "profile");
+	ast_sorcery_object_unregister(geoloc_sorcery, "location");
+
+	if (geoloc_sorcery) {
+		ast_sorcery_unref(geoloc_sorcery);
+	}
+	geoloc_sorcery = NULL;
+
+	return 0;
+}
+
+static int default_profile_create(const char *name)
+{
+	int rc = 0;
+	struct ast_geoloc_profile *profile;
+	char *id = ast_alloca(strlen(name) + 3 /* <, >, NULL */);
+
+	sprintf(id, "<%s>", name); /* Safe */
+	profile = ast_sorcery_alloc(geoloc_sorcery, "profile", id);
+	ast_assert_return(profile != NULL, 0);
+
+	profile->precedence = ast_geoloc_precedence_str_to_enum(name);
+	profile->pidf_element = AST_PIDF_ELEMENT_DEVICE;
+	rc = ast_sorcery_create(geoloc_sorcery, profile);
+	/*
+	 * We're either passing the ref to sorcery or there was an error.
+	 * Either way we need to drop our reference.
+	 */
+	ao2_ref(profile, -1);
+
+	/* ast_assert_return wants a true/false */
+	return rc == 0 ? 1 : 0;
+}
+
+static int geoloc_load_default_profiles(void)
+{
+	/*
+	 * If any of these fail, the module will fail to load
+	 * and clean up the sorcery instance so no error cleanup
+	 * is required here.
+	 */
+	ast_assert_return(default_profile_create("prefer_config"), -1);
+	ast_assert_return(default_profile_create("discard_config"), -1);
+	ast_assert_return(default_profile_create("prefer_incoming"), -1);
+	ast_assert_return(default_profile_create("discard_incoming"), -1);
+
+	return 0;
+}
+
+int geoloc_config_load(void)
+{
+	enum ast_sorcery_apply_result result;
+	int rc = 0;
+
+	if (!(geoloc_sorcery = ast_sorcery_open())) {
+		ast_log(LOG_ERROR, "Failed to open geolocation sorcery\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sorcery_apply_config(geoloc_sorcery, "location");
+	result = ast_sorcery_apply_default(geoloc_sorcery, "location", "config", "geolocation.conf,criteria=type=location");
+	if (result != AST_SORCERY_APPLY_SUCCESS) {
+		ast_log(LOG_ERROR, "Failed to apply defaults for geoloc location object with sorcery\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	rc = ast_sorcery_object_register(geoloc_sorcery, "location", geoloc_location_alloc, NULL, geoloc_location_apply_handler);
+	if (rc != 0) {
+		ast_log(LOG_ERROR, "Failed to register geoloc location object with sorcery\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sorcery_object_field_register(geoloc_sorcery, "location", "type", "", OPT_NOOP_T, 0, 0);
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "location", "format", AST_GEOLOC_FORMAT_NONE,
+		location_format_handler, location_format_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "location", "location_info", NULL,
+		location_location_info_handler, location_location_info_to_str, location_location_info_dup, 0, 0);
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "location", "confidence", NULL,
+		location_confidence_handler, location_confidence_to_str, location_confidence_dup, 0, 0);
+	ast_sorcery_object_field_register(geoloc_sorcery, "location", "location_source", "", OPT_STRINGFIELD_T,
+		0, STRFLDSET(struct ast_geoloc_location, location_source));
+	ast_sorcery_object_field_register(geoloc_sorcery, "location", "method", "", OPT_STRINGFIELD_T,
+		0, STRFLDSET(struct ast_geoloc_location, method));
+
+
+	ast_sorcery_apply_config(geoloc_sorcery, "profile");
+	/*
+	 * The memory backend is used to contain the built-in profiles.
+	 */
+	result = ast_sorcery_apply_wizard_mapping(geoloc_sorcery, "profile", "memory", NULL, 0);
+	if (result == AST_SORCERY_APPLY_FAIL) {
+		ast_log(LOG_ERROR, "Failed to add memory wizard mapping to geoloc profile object\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	result = ast_sorcery_apply_wizard_mapping(geoloc_sorcery, "profile", "config",
+		"geolocation.conf,criteria=type=profile", 0);
+	if (result == AST_SORCERY_APPLY_FAIL) {
+		ast_log(LOG_ERROR, "Failed to add memory wizard mapping to geoloc profile object\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	rc = ast_sorcery_object_register(geoloc_sorcery, "profile", geoloc_profile_alloc, NULL, geoloc_profile_apply_handler);
+	if (rc != 0) {
+		ast_log(LOG_ERROR, "Failed to register geoloc profile object with sorcery\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "type", "", OPT_NOOP_T, 0, 0);
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "pidf_element",
+		pidf_element_names[AST_PIDF_ELEMENT_DEVICE], profile_pidf_element_handler, profile_pidf_element_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "location_reference", "", OPT_STRINGFIELD_T,
+		0, STRFLDSET(struct ast_geoloc_profile, location_reference));
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "profile_precedence", "discard_incoming",
+		profile_precedence_handler, profile_precedence_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "usage_rules", NULL,
+		profile_usage_rules_handler, profile_usage_rules_to_str, profile_usage_rules_dup, 0, 0);
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "location_info_refinement", NULL,
+		profile_location_refinement_handler, profile_location_refinement_to_str, profile_location_refinement_dup, 0, 0);
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "location_variables", NULL,
+		profile_location_variables_handler, profile_location_variables_to_str, profile_location_variables_dup, 0, 0);
+	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "notes", "", OPT_STRINGFIELD_T,
+		0, STRFLDSET(struct ast_geoloc_profile, notes));
+	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "allow_routing_use",
+		"no", OPT_BOOL_T, 1, FLDSET(struct ast_geoloc_profile, allow_routing_use));
+	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "suppress_empty_ca_elements",
+		"no", OPT_BOOL_T, 1, FLDSET(struct ast_geoloc_profile, suppress_empty_ca_elements));
+
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "format", AST_GEOLOC_FORMAT_NONE,
+		profile_format_handler, profile_format_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "location_info", NULL,
+		profile_location_info_handler, profile_location_info_to_str, profile_location_info_dup, 0, 0);
+	ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "confidence", NULL,
+		profile_confidence_handler, profile_confidence_to_str, profile_confidence_dup, 0, 0);
+	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "location_source", "", OPT_STRINGFIELD_T,
+		0, STRFLDSET(struct ast_geoloc_profile, location_source));
+	ast_sorcery_object_field_register(geoloc_sorcery, "profile", "method", "", OPT_STRINGFIELD_T,
+		0, STRFLDSET(struct ast_geoloc_profile, method));
+
+
+	ast_sorcery_load(geoloc_sorcery);
+
+	rc = geoloc_load_default_profiles();
+	if (rc != 0) {
+		ast_log(LOG_ERROR, "Failed to load default geoloc profiles\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+
+	ast_cli_register_multiple(geoloc_location_cli_commands, ARRAY_LEN(geoloc_location_cli_commands));
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int AST_OPTIONAL_API_NAME(ast_geoloc_is_loaded)(void)
+{
+	return 1;
+}
+
diff --git a/res/res_geolocation/geoloc_datastore.c b/res/res_geolocation/geoloc_datastore.c
new file mode 100644
index 0000000000000000000000000000000000000000..4e7a85e8f1a4ddd7ed60dd590e5f01a6aa2c7c6f
--- /dev/null
+++ b/res/res_geolocation/geoloc_datastore.c
@@ -0,0 +1,325 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/datastore.h"
+#include "asterisk/channel.h"
+#include "asterisk/res_geolocation.h"
+#include "asterisk/vector.h"
+#include "geoloc_private.h"
+
+#define GEOLOC_DS_TYPE "geoloc_eprofiles"
+
+struct ast_sorcery *geoloc_sorcery;
+
+struct eprofiles_datastore {
+	const char *id;
+	AST_VECTOR(geoloc_eprofiles, struct ast_geoloc_eprofile *) eprofiles;
+};
+
+static void geoloc_datastore_free(void *obj)
+{
+	struct eprofiles_datastore *eds = obj;
+
+	AST_VECTOR_RESET(&eds->eprofiles, ao2_cleanup);
+	AST_VECTOR_FREE(&eds->eprofiles);
+	ast_free(eds);
+}
+
+static void *geoloc_datastore_duplicate(void *obj)
+{
+	struct eprofiles_datastore *in_eds = obj;
+	struct eprofiles_datastore *out_eds;
+	int rc = 0;
+	int i = 0;
+	int eprofile_count = 0;
+
+	out_eds = ast_calloc(1, sizeof(*out_eds));
+	if (!out_eds) {
+		return NULL;
+	}
+
+	rc = AST_VECTOR_INIT(&out_eds->eprofiles, 2);
+	if (rc != 0) {
+		ast_free(out_eds);
+		return NULL;
+	}
+
+	eprofile_count = AST_VECTOR_SIZE(&in_eds->eprofiles);
+	for (i = 0; i < eprofile_count; i++) {
+		struct ast_geoloc_eprofile *ep = AST_VECTOR_GET(&in_eds->eprofiles, i);
+		rc = AST_VECTOR_APPEND(&out_eds->eprofiles, ao2_bump(ep));
+		if (rc != 0) {
+			/* This will clean up the bumped reference to the eprofile */
+			geoloc_datastore_free(out_eds);
+			return NULL;
+		}
+	}
+
+	return out_eds;
+}
+
+static const struct ast_datastore_info geoloc_datastore_info = {
+	.type = GEOLOC_DS_TYPE,
+	.destroy = geoloc_datastore_free,
+	.duplicate = geoloc_datastore_duplicate,
+};
+
+#define IS_GEOLOC_DS(_ds) (_ds && _ds->data && ast_strings_equal(_ds->info->type, GEOLOC_DS_TYPE))
+
+const char *ast_geoloc_datastore_get_id(struct ast_datastore *ds)
+{
+	struct eprofiles_datastore *eds = NULL;
+
+	if (!IS_GEOLOC_DS(ds)) {
+		return NULL;
+	}
+
+	eds = (struct eprofiles_datastore *)ds->data;
+
+	return eds->id;
+}
+
+struct ast_datastore *ast_geoloc_datastore_create(const char *id)
+{
+	struct ast_datastore *ds = NULL;
+	struct eprofiles_datastore *eds = NULL;
+	int rc = 0;
+
+	if (ast_strlen_zero(id)) {
+		ast_log(LOG_ERROR, "A geoloc datastore can't be allocated with a NULL or empty id\n");
+		return NULL;
+	}
+
+	ds = ast_datastore_alloc(&geoloc_datastore_info, NULL);
+	if (!ds) {
+		ast_log(LOG_ERROR, "Geoloc datastore '%s' couldn't be allocated\n", id);
+		return NULL;
+	}
+
+	eds = ast_calloc(1, sizeof(*eds));
+	if (!eds) {
+		ast_datastore_free(ds);
+		ast_log(LOG_ERROR, "Private structure for geoloc datastore '%s' couldn't be allocated\n", id);
+		return NULL;
+	}
+	ds->data = eds;
+
+
+	rc = AST_VECTOR_INIT(&eds->eprofiles, 2);
+	if (rc != 0) {
+		ast_datastore_free(ds);
+		ast_log(LOG_ERROR, "Vector for geoloc datastore '%s' couldn't be initialized\n", id);
+		return NULL;
+	}
+
+	return ds;
+}
+
+int ast_geoloc_datastore_add_eprofile(struct ast_datastore *ds,
+	struct ast_geoloc_eprofile *eprofile)
+{
+	struct eprofiles_datastore *eds = NULL;
+	int rc = 0;
+
+	if (!IS_GEOLOC_DS(ds) || !eprofile) {
+		return -1;
+	}
+
+	eds = ds->data;
+	rc = AST_VECTOR_APPEND(&eds->eprofiles, ao2_bump(eprofile));
+	if (rc != 0) {
+		ao2_ref(eprofile, -1);
+		ast_log(LOG_ERROR, "Couldn't add eprofile '%s' to geoloc datastore '%s'\n", eprofile->id, eds->id);
+		return -1;
+	}
+
+	return AST_VECTOR_SIZE(&eds->eprofiles);
+}
+
+int ast_geoloc_datastore_insert_eprofile(struct ast_datastore *ds,
+	struct ast_geoloc_eprofile *eprofile, int index)
+{
+	struct eprofiles_datastore *eds = NULL;
+	int rc = 0;
+
+	if (!IS_GEOLOC_DS(ds) || !eprofile) {
+		return -1;
+	}
+
+	eds = ds->data;
+	rc = AST_VECTOR_INSERT_AT(&eds->eprofiles, index, ao2_bump(eprofile));
+	if (rc != 0) {
+		ao2_ref(eprofile, -1);
+		ast_log(LOG_ERROR, "Couldn't add eprofile '%s' to geoloc datastore '%s' in position '%d'\n",
+			eprofile->id, eds->id, index);
+		return -1;
+	}
+
+	return AST_VECTOR_SIZE(&eds->eprofiles);
+}
+
+int ast_geoloc_datastore_size(struct ast_datastore *ds)
+{
+	struct eprofiles_datastore *eds = NULL;
+
+	if (!IS_GEOLOC_DS(ds)) {
+		return -1;
+	}
+
+	eds = ds->data;
+
+	return AST_VECTOR_SIZE(&eds->eprofiles);
+}
+
+int ast_geoloc_datastore_set_inheritance(struct ast_datastore *ds, int inherit)
+{
+	if (!IS_GEOLOC_DS(ds)) {
+		return -1;
+	}
+	ds->inheritance = inherit ? DATASTORE_INHERIT_FOREVER : 0;
+	return 0;
+}
+
+struct ast_geoloc_eprofile *ast_geoloc_datastore_get_eprofile(struct ast_datastore *ds, int ix)
+{
+	struct eprofiles_datastore *eds = NULL;
+	struct ast_geoloc_eprofile *eprofile;
+
+	if (!IS_GEOLOC_DS(ds)) {
+		return NULL;
+	}
+
+	eds = ds->data;
+
+	if (ix >= AST_VECTOR_SIZE(&eds->eprofiles)) {
+		return NULL;
+	}
+
+	eprofile  = AST_VECTOR_GET(&eds->eprofiles, ix);
+	return ao2_bump(eprofile);
+}
+
+struct ast_datastore *ast_geoloc_datastore_find(struct ast_channel *chan)
+{
+	return ast_channel_datastore_find(chan, &geoloc_datastore_info, NULL);
+}
+
+int ast_geoloc_datastore_delete_eprofile(struct ast_datastore *ds, int ix)
+{
+	struct eprofiles_datastore *eds = NULL;
+
+	if (!IS_GEOLOC_DS(ds)) {
+		return -1;
+	}
+
+	eds = ds->data;
+
+	if (ix >= AST_VECTOR_SIZE(&eds->eprofiles)) {
+		return -1;
+	}
+
+	ao2_ref(AST_VECTOR_REMOVE(&eds->eprofiles, ix, 1), -1);
+	return 0;
+}
+
+struct ast_datastore *ast_geoloc_datastore_create_from_eprofile(
+	struct ast_geoloc_eprofile *eprofile)
+{
+	struct ast_datastore *ds;
+	int rc = 0;
+
+	if (!eprofile) {
+		return NULL;
+	}
+
+	ds = ast_geoloc_datastore_create(eprofile->id);
+	if (!ds) {
+		return NULL;
+	}
+
+	rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
+	if (rc <= 0) {
+		ast_datastore_free(ds);
+		ds = NULL;
+	}
+
+	return ds;
+}
+
+struct ast_datastore *ast_geoloc_datastore_create_from_profile_name(const char *profile_name)
+{
+	struct ast_datastore *ds = NULL;
+	struct ast_geoloc_eprofile *eprofile = NULL;
+	struct ast_geoloc_profile *profile = NULL;
+	int rc = 0;
+
+	if (ast_strlen_zero(profile_name)) {
+		return NULL;
+	}
+
+	profile = ast_sorcery_retrieve_by_id(geoloc_sorcery, "profile", profile_name);
+	if (!profile) {
+		ast_log(LOG_ERROR, "A profile with the name '%s' was not found\n", profile_name);
+		return NULL;
+	}
+
+	ds = ast_geoloc_datastore_create(profile_name);
+	if (!ds) {
+		ast_log(LOG_ERROR, "A datastore couldn't be allocated for profile '%s'\n", profile_name);
+		ao2_ref(profile, -1);
+		return NULL;
+	}
+
+	eprofile = ast_geoloc_eprofile_create_from_profile(profile);
+	ao2_ref(profile, -1);
+	if (!eprofile) {
+		ast_datastore_free(ds);
+		ast_log(LOG_ERROR, "An effective profile with the name '%s' couldn't be allocated\n", profile_name);
+		return NULL;
+	}
+
+	rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
+	ao2_ref(eprofile, -1);
+	if (rc <= 0) {
+		ast_datastore_free(ds);
+		ds = NULL;
+	}
+
+	return ds;
+}
+
+int geoloc_channel_unload(void)
+{
+	if (geoloc_sorcery) {
+		ast_sorcery_unref(geoloc_sorcery);
+	}
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_channel_load(void)
+{
+	geoloc_sorcery = geoloc_get_sorcery();
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_channel_reload(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
diff --git a/res/res_geolocation/geoloc_dialplan.c b/res/res_geolocation/geoloc_dialplan.c
new file mode 100644
index 0000000000000000000000000000000000000000..1d1346a30d28d9574f7e4dab58d49ca18ea54b8c
--- /dev/null
+++ b/res/res_geolocation/geoloc_dialplan.c
@@ -0,0 +1,376 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "asterisk/config.h"
+#include "asterisk/cli.h"
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/strings.h"
+#include "asterisk/utils.h"
+#include "asterisk/app.h"
+#include "geoloc_private.h"
+
+static void varlist_to_str(struct ast_variable *list, struct ast_str** buf, size_t len)
+{
+	struct ast_variable *var = list;
+
+	for (; var; var = var->next) {
+		ast_str_append(buf, len, "%s=\"%s\"%s", var->name, var->value, var->next ? "," : "");
+	}
+}
+
+#define RESOLVE_FOR_READ(_param) \
+({ \
+	if (ast_test_flag(&opts, OPT_GEOLOC_RESOLVE)) { \
+		struct ast_variable *resolved = geoloc_eprofile_resolve_varlist( \
+			eprofile->_param, eprofile->location_variables, chan); \
+		if (!resolved) { \
+			ast_log(LOG_ERROR, "%s: Unable to resolve " #_param "\n", chan_name); \
+			pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); \
+			return 0; \
+		} \
+		varlist_to_str(resolved, buf, len); \
+		ast_variables_destroy(resolved); \
+	} else { \
+		varlist_to_str(eprofile->_param, buf, len); \
+	} \
+})
+
+enum my_app_option_flags {
+	OPT_GEOLOC_RESOLVE = (1 << 0),
+	OPT_GEOLOC_APPEND = (1 << 1),
+};
+
+AST_APP_OPTIONS(action_options, {
+	AST_APP_OPTION('r', OPT_GEOLOC_RESOLVE),
+	AST_APP_OPTION('a', OPT_GEOLOC_APPEND),
+});
+
+
+static int geoloc_profile_read(struct ast_channel *chan,
+	const char *cmd, char *data, struct ast_str **buf, ssize_t len)
+{
+	char *parsed_data = ast_strdupa(data);
+	const char *chan_name = ast_channel_name(chan);
+	struct ast_datastore *ds;
+	struct ast_geoloc_eprofile *orig_eprofile = NULL;
+	struct ast_geoloc_eprofile *eprofile = NULL;
+	struct ast_flags opts = { 0, };
+
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(field);
+		AST_APP_ARG(options);
+	);
+
+	/* Check for zero arguments */
+	if (ast_strlen_zero(parsed_data)) {
+		ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", chan_name);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
+		return 0;
+	}
+
+	AST_STANDARD_APP_ARGS(args, parsed_data);
+
+	if (ast_strlen_zero(args.field)) {
+		ast_log(LOG_ERROR, "%s: Cannot call without a field to query\n", chan_name);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
+		return 0;
+	}
+
+	if (!ast_strlen_zero(args.options)) {
+		if (ast_app_parse_options(action_options, &opts, NULL, args.options)) {
+			ast_log(LOG_ERROR, "%s: Invalid options: %s\n", chan_name, args.options);
+			pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
+			return 0;
+		}
+	}
+
+	ds = ast_geoloc_datastore_find(chan);
+	if (!ds) {
+		ast_log(LOG_NOTICE, "%s: There is no geoloc profile on this channel\n", chan_name);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
+		return 0;
+	}
+
+	orig_eprofile = ast_geoloc_datastore_get_eprofile(ds, 0);
+	if (!orig_eprofile) {
+		ast_log(LOG_NOTICE, "%s: There is no geoloc profile on this channel\n", chan_name);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
+		return 0;
+	}
+
+	eprofile = ast_geoloc_eprofile_dup(orig_eprofile);
+	ao2_ref(orig_eprofile, -1);
+	if (!eprofile) {
+		ast_log(LOG_ERROR, "%s: Unable to duplicate eprofile\n", chan_name);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
+		return 0;
+	}
+
+	if (!eprofile->effective_location) {
+		ast_geoloc_eprofile_refresh_location(eprofile);
+	}
+
+	pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "0");
+	if (ast_strings_equal(args.field, "inheritable")) {
+		ast_str_append(buf, len, "%s", ds->inheritance ? "true" : "false");
+	} else if (ast_strings_equal(args.field, "id")) {
+		ast_str_append(buf, len, "%s", eprofile->id);
+	} else if (ast_strings_equal(args.field, "location_reference")) {
+		ast_str_append(buf, len, "%s", eprofile->location_reference);
+	} else if (ast_strings_equal(args.field, "method")) {
+		ast_str_append(buf, len, "%s", eprofile->method);
+	} else if (ast_strings_equal(args.field, "allow_routing_use")) {
+		ast_str_append(buf, len, "%s", eprofile->allow_routing_use ? "yes" : "no");
+	} else if (ast_strings_equal(args.field, "suppress_empty_ca_elements")) {
+		ast_str_append(buf, len, "%s", eprofile->suppress_empty_ca_elements ? "yes" : "no");
+	} else if (ast_strings_equal(args.field, "profile_precedence")) {
+		ast_str_append(buf, len, "%s", ast_geoloc_precedence_to_name(eprofile->precedence));
+	} else if (ast_strings_equal(args.field, "format")) {
+		ast_str_append(buf, len, "%s", ast_geoloc_format_to_name(eprofile->format));
+	} else if (ast_strings_equal(args.field, "pidf_element")) {
+		ast_str_append(buf, len, "%s", ast_geoloc_pidf_element_to_name(eprofile->pidf_element));
+	} else if (ast_strings_equal(args.field, "location_source")) {
+		ast_str_append(buf, len, "%s", eprofile->location_source);
+	} else if (ast_strings_equal(args.field, "notes")) {
+		ast_str_append(buf, len, "%s", eprofile->notes);
+	} else if (ast_strings_equal(args.field, "location_info")) {
+		RESOLVE_FOR_READ(location_info);
+	} else if (ast_strings_equal(args.field, "location_info_refinement")) {
+		RESOLVE_FOR_READ(location_refinement);
+	} else if (ast_strings_equal(args.field, "location_variables")) {
+		RESOLVE_FOR_READ(location_variables);
+	} else if (ast_strings_equal(args.field, "effective_location")) {
+		RESOLVE_FOR_READ(effective_location);
+	} else if (ast_strings_equal(args.field, "usage_rules")) {
+		RESOLVE_FOR_READ(usage_rules);
+	} else if (ast_strings_equal(args.field, "confidence")) {
+		varlist_to_str(eprofile->confidence, buf, len);
+	} else {
+		ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", chan_name, args.field);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3");
+	}
+
+	ao2_ref(eprofile, -1);
+	return 0;
+}
+
+#define VAR_LIST_REPLACE(_old, _new) \
+	ast_variables_destroy(_old); \
+	_old = _new;
+
+#define TEST_ENUM_VALUE(_chan_name, _ep, _field, _value) \
+({ \
+	enum ast_geoloc_ ## _field v; \
+	v = ast_geoloc_ ## _field ## _str_to_enum(_value); \
+	if (v == AST_GEOLOC_INVALID_VALUE) { \
+		ast_log(LOG_ERROR, "%s: %s '%s' is invalid\n", _chan_name, #_field, value); \
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); \
+		return 0; \
+	} \
+	_ep->_field = v; \
+})
+
+#define TEST_VARLIST(_chan_name, _ep, _field, _value) \
+({ \
+	struct ast_variable *_list; \
+	_list = ast_variable_list_from_quoted_string(_value, ",", "=", "\"" ); \
+	if (!_list) { \
+		ast_log(LOG_ERROR, "%s: %s '%s' is malformed or contains invalid values", _chan_name, #_field, _value); \
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); \
+		return 0; \
+	} \
+	if (ast_test_flag(&opts, OPT_GEOLOC_APPEND)) { \
+		ast_variable_list_append(&_ep->_field, _list); \
+	} else {\
+		VAR_LIST_REPLACE(_ep->_field, _list); \
+	} \
+})
+
+
+#define RESOLVE_FOR_WRITE(_param) \
+({ \
+if (ast_test_flag(&opts, OPT_GEOLOC_RESOLVE)) { \
+	struct ast_variable *resolved = geoloc_eprofile_resolve_varlist( \
+		eprofile->_param, eprofile->location_variables, chan); \
+	if (!resolved) { \
+		ast_log(LOG_ERROR, "%s: Unable to resolve " #_param " %p %p\n", chan_name, eprofile->_param, eprofile->location_variables); \
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3"); \
+		return 0; \
+	} \
+	VAR_LIST_REPLACE(eprofile->_param, resolved); \
+} \
+})
+
+static int geoloc_profile_write(struct ast_channel *chan, const char *cmd, char *data,
+	 const char *value)
+{
+	char *parsed_data = ast_strdupa(data);
+	const char *chan_name = ast_channel_name(chan);
+	struct ast_datastore *ds; /* Reminder: datastores aren't ao2 objects */
+	RAII_VAR(struct ast_geoloc_eprofile *, eprofile, NULL, ao2_cleanup);
+	struct ast_flags opts = { 0, };
+
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(field);
+		AST_APP_ARG(options);
+	);
+
+	/* Check for zero arguments */
+	if (ast_strlen_zero(parsed_data)) {
+		ast_log(LOG_ERROR, "%s: Cannot call without arguments\n", chan_name);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
+		return 0;
+	}
+
+	AST_STANDARD_APP_ARGS(args, parsed_data);
+
+	if (ast_strlen_zero(args.field)) {
+		ast_log(LOG_ERROR, "%s: Cannot call without a field to set\n", chan_name);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
+		return 0;
+	}
+
+	if (!ast_strlen_zero(args.options)) {
+		if (ast_app_parse_options(action_options, &opts, NULL, args.options)) {
+			ast_log(LOG_ERROR, "%s: Invalid options: %s\n", chan_name, args.options);
+			pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-1");
+			return 0;
+		}
+	}
+
+	ast_debug(1, "%s: name: %s value: %s  options: %s append: %s resolve: %s\n", chan_name,
+		args.field, value, args.options, ast_test_flag(&opts, OPT_GEOLOC_APPEND) ? "yes" : "no",
+			ast_test_flag(&opts, OPT_GEOLOC_RESOLVE) ? "yes" : "no");
+
+	ds = ast_geoloc_datastore_find(chan);
+	if (!ds) {
+		ds = ast_geoloc_datastore_create(ast_channel_name(chan));
+		if (!ds) {
+			ast_log(LOG_WARNING, "%s: Unable to create geolocation datastore\n", chan_name);
+			pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
+			return 0;
+		}
+		ast_channel_datastore_add(chan, ds);
+	}
+
+	eprofile = ast_geoloc_datastore_get_eprofile(ds, 0);
+	if (!eprofile) {
+		int rc;
+		eprofile = ast_geoloc_eprofile_alloc(chan_name);
+		if (!eprofile) {
+			ast_log(LOG_ERROR, "%s: Could not allocate eprofile\n", chan_name);
+			pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
+			return 0;
+		}
+		rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
+		if (rc <= 0) {
+			ast_log(LOG_ERROR, "%s: Could not add eprofile to datastore\n", chan_name);
+			pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-2");
+			return 0;
+		}
+	}
+
+	if (ast_strings_equal(args.field, "inheritable")) {
+		ast_geoloc_datastore_set_inheritance(ds, ast_true(value));
+	} else if (ast_strings_equal(args.field, "id")) {
+		ast_string_field_set(eprofile, id, value);
+	} else if (ast_strings_equal(args.field, "location_reference")) {
+		struct ast_geoloc_location *loc = ast_geoloc_get_location(value);
+		ao2_cleanup(loc);
+		if (!loc) {
+			ast_log(LOG_ERROR, "%s: Location reference '%s' doesn't exist\n", chan_name, value);
+			pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3");
+			return 0;
+		}
+		ast_string_field_set(eprofile, location_reference, value);
+	} else if (ast_strings_equal(args.field, "method")) {
+		ast_string_field_set(eprofile, method, value);
+	} else if (ast_strings_equal(args.field, "allow_routing_use")) {
+		eprofile->allow_routing_use = ast_true(value);
+	} else if (ast_strings_equal(args.field, "suppress_empty_ca_elements")) {
+		eprofile->suppress_empty_ca_elements = ast_true(value);
+	} else if (ast_strings_equal(args.field, "profile_precedence")) {
+		TEST_ENUM_VALUE(chan_name, eprofile, precedence, value);
+	} else if (ast_strings_equal(args.field, "format")) {
+		TEST_ENUM_VALUE(chan_name, eprofile, format, value);
+	} else if (ast_strings_equal(args.field, "pidf_element")) {
+		TEST_ENUM_VALUE(chan_name, eprofile, pidf_element, value);
+	} else if (ast_strings_equal(args.field, "location_source")) {
+		ast_string_field_set(eprofile, location_source, value);
+	} else if (ast_strings_equal(args.field, "notes")) {
+		ast_string_field_set(eprofile, notes, value);
+	} else if (ast_strings_equal(args.field, "location_info")) {
+		TEST_VARLIST(chan_name, eprofile, location_info, value);
+		RESOLVE_FOR_WRITE(location_info);
+	} else if (ast_strings_equal(args.field, "location_info_refinement")) {
+		TEST_VARLIST(chan_name, eprofile, location_refinement, value);
+		RESOLVE_FOR_WRITE(location_refinement);
+	} else if (ast_strings_equal(args.field, "location_variables")) {
+		TEST_VARLIST(chan_name, eprofile, location_variables, value);
+		RESOLVE_FOR_WRITE(location_variables);
+	} else if (ast_strings_equal(args.field, "effective_location")) {
+		TEST_VARLIST(chan_name, eprofile, effective_location, value);
+		RESOLVE_FOR_WRITE(effective_location);
+	} else if (ast_strings_equal(args.field, "usage_rules")) {
+		TEST_VARLIST(chan_name, eprofile, usage_rules, value);
+		RESOLVE_FOR_WRITE(usage_rules);
+	} else if (ast_strings_equal(args.field, "confidence")) {
+		TEST_VARLIST(chan_name, eprofile, confidence, value);
+	} else {
+		ast_log(LOG_ERROR, "%s: Field '%s' is not valid\n", chan_name, args.field);
+		pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "-3");
+		return 0;
+	}
+
+	ast_geoloc_eprofile_refresh_location(eprofile);
+
+	pbx_builtin_setvar_helper(chan, "GEOLOCPROFILESTATUS", "0");
+
+	return 0;
+}
+
+static struct ast_custom_function geoloc_function = {
+	.name = "GEOLOC_PROFILE",
+	.read2 = geoloc_profile_read,
+	.write = geoloc_profile_write,
+};
+
+int geoloc_dialplan_unload(void)
+{
+	ast_custom_function_unregister(&geoloc_function);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_dialplan_load(void)
+{
+	int res = 0;
+
+	res = ast_custom_function_register(&geoloc_function);
+
+	return res == 0 ? AST_MODULE_LOAD_SUCCESS : AST_MODULE_LOAD_DECLINE;
+}
+
+int geoloc_dialplan_reload(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
diff --git a/res/res_geolocation/geoloc_doc.xml b/res/res_geolocation/geoloc_doc.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b5713362f2405294487c26a15ea1fd436b486a45
--- /dev/null
+++ b/res/res_geolocation/geoloc_doc.xml
@@ -0,0 +1,310 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE docs SYSTEM "appdocsxml.dtd">
+<docs>
+	<configInfo name="res_geolocation" language="en_US">
+		<synopsis>Core Geolocation Support</synopsis>
+		<configFile name="geolocation.conf">
+			<configObject name="location">
+				<synopsis>Location</synopsis>
+				<description>
+					<para>Parameters for defining a Location object</para>
+				</description>
+
+				<configOption name="type">
+					<synopsis>Must be of type 'location'.</synopsis>
+				</configOption>
+
+				<configOption name="format" default="none">
+					<synopsis>Location specification type</synopsis>
+					<description>
+						<enumlist>
+							<enum name="civicAddress">
+								<para>
+									The <literal>location_info</literal>
+									parameter must contain a comma separated list of IANA codes
+									or synonyms describing the civicAddress of this location.
+									The IANA codes and synonyms can be obtained by executing
+									the CLI command <literal>geoloc show civicAddr_mapping</literal>.
+								</para>
+							</enum>
+							<enum name="GML">
+								<para>
+									The
+									<literal>location_info</literal> parameter must contain a comma
+									separated list valid GML elements describing this location.
+								</para>
+							</enum>
+							<enum name="URI">
+								<para>
+									The
+									<literal>location_info</literal> parameter must contain a single
+									URI parameter which contains an external URI describing this location.
+								</para>
+							</enum>
+						</enumlist>
+					</description>
+				</configOption>
+
+				<configOption name="location_info" default="none">
+					<synopsis>Location information</synopsis>
+					<description>
+						<para>The contents of this parameter are specific to the
+							location <literal>format</literal>.</para>
+						<enumlist>
+						<enum name="civicAddress">
+						<para>
+						location_info = country=US,A1="New York",city_district=Manhattan,
+						A3="New York", house_number=1633, street=46th, street_suffix = Street,
+						postal_code=10222,floor=20,room=20A2
+						</para>
+						</enum>
+						<enum name="GML">
+						<para>
+						location_info = Shape=Sphere, pos3d="39.12345 -105.98766 1920", radius=200
+						</para>
+						</enum>
+						<enum name="URI">
+						<para>
+						location_info = URI=https:/something.com?exten=${EXTEN}
+						</para>
+						</enum>
+						</enumlist>
+					</description>
+				</configOption>
+
+				<configOption name="location_source" default="none">
+					<synopsis>Fully qualified host name</synopsis>
+					<description>
+						<para>This parameter isn't required but if provided, RFC8787 says it MUST be a fully
+						qualified host name.  IP addresses are specifically NOT allowed.  The value will be placed
+						in a <literal>loc-src</literal> parameter appended to the URI in the <literal>
+						Geolocation</literal> header.</para>
+					</description>
+				</configOption>
+
+				<configOption name="method" default="none">
+					<synopsis>Location determination method</synopsis>
+					<description>
+						<para>This is a rarely used field in the specification that would
+						indicate the method used to determine the location.  Its usage and values should be
+						pre-negotiated with any recipients.</para>
+						<enumlist>
+						<enum name="GPS"/>
+						<enum name="A-GPS"/>
+						<enum name="Manual"/>
+						<enum name="DHCP"/>
+						<enum name="Triangulation"/>
+						<enum name="Cell"/>
+						<enum name="802.11"/>
+						</enumlist>
+					</description>
+				</configOption>
+
+				<configOption name="confidence" default="none">
+					<synopsis>Level of confidence</synopsis>
+					<description>
+						<para>This is a rarely used field in the specification that would
+						indicate the confidence in the location specified.  See RFC7459
+						for exact details.
+						</para>
+						<para>
+						Sub-parameters:
+						</para>
+						<enumlist>
+						<enum name="pdf">
+						<para>One of:</para>
+						<enumlist>
+						<enum name="unknown"/>
+						<enum name="normal"/>
+						<enum name="rectangular"/>
+						</enumlist>
+						</enum>
+						<enum name="value">
+						<para>A percentage indicating the confidence.</para>
+						</enum>
+						</enumlist>
+					</description>
+					<see-also>
+						<ref type="link">https://www.rfc-editor.org/rfc/rfc7459</ref>
+					</see-also>
+				</configOption>
+			</configObject>
+
+			<configObject name="profile">
+				<synopsis>Profile</synopsis>
+				<description>
+					<para>Parameters for defining a Profile object</para>
+				</description>
+				<configOption name="type">
+					<synopsis>Must be of type 'profile'.</synopsis>
+				</configOption>
+
+				<configOption name="pidf_element" default="device">
+					<synopsis>PIDF-LO element to place this profile in</synopsis>
+					<description>
+						<enumlist>
+							<enum name="tuple" />
+							<enum name="device" />
+							<enum name="person" />
+						</enumlist>
+						<para>
+							Based on RFC5491 (see below) the recommended and default element
+							is <literal>device</literal>.
+						</para>
+					</description>
+					<see-also>
+						<ref type="link">https://www.rfc-editor.org/rfc/rfc5491.html#section-3.4</ref>
+					</see-also>
+				</configOption>
+
+				<configOption name="location_reference" default="none">
+					<synopsis>Reference to a location object</synopsis>
+				</configOption>
+
+				<configOption name="location_info_refinement" default="none">
+					<synopsis>Reference to a location object</synopsis>
+				</configOption>
+				<configOption name="location_variables" default="none">
+					<synopsis>Reference to a location object</synopsis>
+				</configOption>
+
+				<configOption name="usage_rules" default="empty &lt;usage_rules&gt; element">
+					<synopsis>location specification type</synopsis>
+					<description>
+						<para>xxxx</para>
+					</description>
+				</configOption>
+
+				<configOption name="notes" default="">
+					<synopsis>Notes to be added to the outgoing PIDF-LO document</synopsis>
+					<description>
+						<para>The specification of this parameter will cause a
+						<literal>&lt;note-well&gt;</literal> element to be added to the
+						outgoing PIDF-LO document.  Its usage should be pre-negotiated with
+						any recipients.</para>
+					</description>
+
+				</configOption>
+				<configOption name="allow_routing_use" default="no">
+					<synopsis>Sets the value of the Geolocation-Routing header.</synopsis>
+				</configOption>
+
+				<configOption name="suppress_empty_ca_elements" default="no">
+					<synopsis>Sets if empty Civic Address elements should be suppressed
+					from the PIDF-LO document.</synopsis>
+				</configOption>
+
+				<configOption name="profile_precedence" default="discard_incoming">
+					<synopsis>Determine which profile on a channel should be used</synopsis>
+					<description>
+						<enumlist>
+							<enum name="prefer_incoming">
+							<para>Use the incoming profile if it exists and has location information, otherwise use the
+							configured profile if it exists and has location information. If neither profile has location
+							information, nothing is sent.
+							</para></enum>
+							<enum name="prefer_config">
+							<para>Use the configured profile if it exists and has location information, otherwise use the
+							incoming profile if it exists and has location information. If neither profile has location
+							information, nothing is sent.
+							</para></enum>
+							<enum name="discard_incoming"
+							><para>Discard any incoming profile and use the configured profile if it exists and
+							it has location information.  If the configured profile doesn't exist or has no
+							location information, nothing is sent.
+							</para></enum>
+							<enum name="discard_config">
+							<para>Discard any configured profile and use the incoming profile if it exists and
+							it has location information.  If the incoming profile doesn't exist or has no
+							location information, nothing is sent.
+							</para></enum>
+						</enumlist>
+					</description>
+				</configOption>
+
+				<xi:include xpointer="xpointer(/docs/configInfo[@name='res_geolocation']/configFile[@name='geolocation.conf']/configObject[@name='location']/configOption[@name='format'])"/>
+				<xi:include xpointer="xpointer(/docs/configInfo[@name='res_geolocation']/configFile[@name='geolocation.conf']/configObject[@name='location']/configOption[@name='location_info'])"/>
+				<xi:include xpointer="xpointer(/docs/configInfo[@name='res_geolocation']/configFile[@name='geolocation.conf']/configObject[@name='location']/configOption[@name='confidence'])"/>
+				<xi:include xpointer="xpointer(/docs/configInfo[@name='res_geolocation']/configFile[@name='geolocation.conf']/configObject[@name='location']/configOption[@name='location_source'])"/>
+				<xi:include xpointer="xpointer(/docs/configInfo[@name='res_geolocation']/configFile[@name='geolocation.conf']/configObject[@name='location']/configOption[@name='method'])"/>
+			</configObject>
+		</configFile>
+	</configInfo>
+	<function name="GEOLOC_PROFILE" language="en_US">
+		<synopsis>
+			Get or Set a field in a geolocation profile
+		</synopsis>
+		<syntax>
+			<parameter name="parameter" required="true">
+				<para>The profile parameter to operate on. The following fields from the
+				Location and Profile objects are supported.</para>
+				<enumlist>
+					<enum name="id"/>
+					<enum name="location_reference"/>
+					<enum name="method"/>
+					<enum name="allow_routing_use"/>
+					<enum name="profile_precedence"/>
+					<enum name="format"/>
+					<enum name="pidf_element"/>
+					<enum name="location_source"/>
+					<enum name="notes"/>
+					<enum name="location_info"/>
+					<enum name="location_info_refinement"/>
+					<enum name="location_variables"/>
+					<enum name="effective_location"/>
+					<enum name="usage_rules"/>
+					<enum name="confidence"/>
+				</enumlist>
+				<para>Additionally, the <literal>inheritable</literal> field may be
+				set to <literal>true</literal> or <literal>false</literal> to control
+				whether the profile will be passed to the outgoing channel.
+				</para>
+				<para>
+				</para>
+			</parameter>
+
+			<parameter name="options" required="false">
+				<optionlist>
+				<option name="a">
+					<para>Append provided value to the specified parameter
+					instead of replacing the existing value.  This only applies
+					to variable list parameters like
+					<literal>location_info_refinement</literal>.
+					</para>
+				</option>
+				<option name="r">
+					<para>Before reading or after writing the specified parameter,
+					re-resolve the <literal>effective_location</literal> and
+					<literal>usage_rules</literal> parameters using the
+					<literal>location_variables</literal> parameter and the variables
+					set on the channel in effect at the time this function is called.
+					</para>
+					<note><para>On a read operation, this does not alter the actual profile
+						in any way.  On a write operation however, the
+						<literal>effective_location</literal> and/or <literal>usage_rules</literal>
+						parameters may indeed change and those changes will be passed on
+						to any outgoing channel.
+					</para></note>
+				</option>
+				</optionlist>
+			</parameter>
+		</syntax>
+		<description><para>
+		When used to set a parameter on a profile, if the profile doesn't already exist, a new
+		one will be created automatically.
+		</para>
+		<para>
+		The <literal>${GEOLOCPROFILESTATUS}</literal> channel variable will be set with
+		a return code indicating the result of the operation.  Possible values are:
+		</para>
+		<enumlist>
+			<enum name="0"><para>Success</para></enum>
+			<enum name="-1"><para>No or not enough parameters were supplied</para></enum>
+			<enum name="-2"><para>There was an internal error finding or creating a profile</para></enum>
+			<enum name="-3"><para>There was an issue specific to the parameter specified
+			(value not valid or parameter name not found, etc.)</para></enum>
+		</enumlist>
+		</description>
+	</function>
+</docs>
+
diff --git a/res/res_geolocation/geoloc_eprofile.c b/res/res_geolocation/geoloc_eprofile.c
new file mode 100644
index 0000000000000000000000000000000000000000..3d10d12ab4b38c21ee41b80a3827f82f3cb2c044
--- /dev/null
+++ b/res/res_geolocation/geoloc_eprofile.c
@@ -0,0 +1,1343 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "asterisk/pbx.h"
+#include "asterisk/strings.h"
+#include "asterisk/xml.h"
+#include "geoloc_private.h"
+
+extern const uint8_t _binary_res_geolocation_pidf_to_eprofile_xslt_start[];
+extern const uint8_t _binary_res_geolocation_pidf_to_eprofile_xslt_end[];
+static size_t pidf_to_eprofile_xslt_size;
+
+extern const uint8_t _binary_res_geolocation_pidf_lo_test_xml_start[];
+extern const uint8_t _binary_res_geolocation_pidf_lo_test_xml_end[];
+static size_t pidf_lo_test_xml_size;
+
+extern const uint8_t _binary_res_geolocation_eprofile_to_pidf_xslt_start[];
+extern const uint8_t _binary_res_geolocation_eprofile_to_pidf_xslt_end[];
+static size_t eprofile_to_pidf_xslt_size;
+
+static struct ast_xslt_doc *eprofile_to_pidf_xslt;
+static struct ast_xslt_doc *pidf_to_eprofile_xslt;
+
+static struct ast_sorcery *geoloc_sorcery;
+
+#define DUP_VARS(_dest, _source) \
+({ \
+	int _rc = 0; \
+	if (_source) { \
+		struct ast_variable *_vars = ast_variables_dup(_source); \
+		if (!_vars) { \
+			_rc = -1; \
+		} else { \
+			_dest = _vars; \
+		} \
+	} \
+	(_rc); \
+})
+
+static void geoloc_eprofile_destructor(void *obj)
+{
+	struct ast_geoloc_eprofile *eprofile = obj;
+
+	ast_string_field_free_memory(eprofile);
+	ast_variables_destroy(eprofile->location_info);
+	ast_variables_destroy(eprofile->location_refinement);
+	ast_variables_destroy(eprofile->location_variables);
+	ast_variables_destroy(eprofile->effective_location);
+	ast_variables_destroy(eprofile->usage_rules);
+	ast_variables_destroy(eprofile->confidence);
+}
+
+struct ast_geoloc_eprofile *ast_geoloc_eprofile_alloc(const char *name)
+{
+	struct ast_geoloc_eprofile *eprofile = ao2_alloc_options(sizeof(*eprofile),
+		geoloc_eprofile_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
+
+	ast_string_field_init(eprofile, 256);
+	ast_string_field_set(eprofile, id, name); /* SAFE string fields handle NULL */
+
+	return eprofile;
+}
+
+int ast_geoloc_eprofile_refresh_location(struct ast_geoloc_eprofile *eprofile)
+{
+	struct ast_geoloc_location *loc = NULL;
+	RAII_VAR(struct ast_variable *, temp_locinfo, NULL, ast_variables_destroy);
+	RAII_VAR(struct ast_variable *, temp_effloc, NULL, ast_variables_destroy);
+	RAII_VAR(struct ast_variable *, temp_confidence, NULL, ast_variables_destroy);
+	const char *method = NULL;
+	const char *location_source = NULL;
+	enum ast_geoloc_format format;
+	struct ast_variable *var;
+	int rc = 0;
+
+	if (!eprofile) {
+		return -1;
+	}
+
+	if (!ast_strlen_zero(eprofile->location_reference)) {
+		loc = ast_sorcery_retrieve_by_id(geoloc_sorcery, "location", eprofile->location_reference);
+		if (!loc) {
+			ast_log(LOG_ERROR, "Profile '%s' referenced location '%s' does not exist!", eprofile->id,
+				eprofile->location_reference);
+			return -1;
+		}
+
+		format = loc->format;
+		method = loc->method;
+		location_source = loc->location_source;
+		rc = DUP_VARS(temp_locinfo, loc->location_info);
+		if (rc == 0) {
+			rc = DUP_VARS(temp_confidence, loc->confidence);
+		}
+		ao2_ref(loc, -1);
+		if (rc != 0) {
+			return -1;
+		}
+	} else {
+		format = eprofile->format;
+		method = eprofile->method;
+		location_source = eprofile->location_source;
+		rc = DUP_VARS(temp_locinfo, eprofile->location_info);
+		if (rc == 0) {
+			rc = DUP_VARS(temp_confidence, eprofile->confidence);
+		}
+		if (rc != 0) {
+			return -1;
+		}
+	}
+
+	rc = DUP_VARS(temp_effloc, temp_locinfo);
+	if (rc != 0) {
+		return -1;
+	}
+
+	if (eprofile->location_refinement) {
+		for (var = eprofile->location_refinement; var; var = var->next) {
+			struct ast_variable *newvar = ast_variable_new(var->name, var->value, "");
+			if (!newvar) {
+				return -1;
+			}
+			if (ast_variable_list_replace(&temp_effloc, newvar)) {
+				ast_variable_list_append(&temp_effloc, newvar);
+			}
+		}
+	}
+
+	eprofile->format = format;
+	ast_string_field_set(eprofile, method, method);
+	ast_string_field_set(eprofile, location_source, location_source);
+
+	ast_variables_destroy(eprofile->location_info);
+	eprofile->location_info = temp_locinfo;
+	temp_locinfo = NULL;
+	ast_variables_destroy(eprofile->effective_location);
+	eprofile->effective_location = temp_effloc;
+	temp_effloc = NULL;
+
+	return 0;
+}
+
+struct ast_geoloc_eprofile *ast_geoloc_eprofile_dup(struct ast_geoloc_eprofile *src)
+{
+	struct ast_geoloc_eprofile *eprofile;
+	const char *profile_id;
+	int rc = 0;
+
+	if (!src) {
+		return NULL;
+	}
+
+	profile_id = ast_strdupa(src->id);
+
+	eprofile = ast_geoloc_eprofile_alloc(profile_id);
+	if (!eprofile) {
+		return NULL;
+	}
+
+	eprofile->allow_routing_use = src->allow_routing_use;
+	eprofile->pidf_element = src->pidf_element;
+	eprofile->suppress_empty_ca_elements = src->suppress_empty_ca_elements;
+	eprofile->format = src->format;
+	eprofile->precedence = src->precedence;
+
+
+	rc = ast_string_field_set(eprofile, location_reference, src->location_reference);
+	if (rc == 0) {
+		ast_string_field_set(eprofile, notes, src->notes);
+	}
+	if (rc == 0) {
+		ast_string_field_set(eprofile, method, src->method);
+	}
+	if (rc == 0) {
+		ast_string_field_set(eprofile, location_source, src->location_source);
+	}
+	if (rc == 0) {
+		rc = DUP_VARS(eprofile->location_info, src->location_info);
+	}
+	if (rc == 0) {
+		rc = DUP_VARS(eprofile->effective_location, src->effective_location);
+	}
+	if (rc == 0) {
+		rc = DUP_VARS(eprofile->location_refinement, src->location_refinement);
+	}
+	if (rc == 0) {
+		rc = DUP_VARS(eprofile->location_variables, src->location_variables);
+	}
+	if (rc == 0) {
+		rc = DUP_VARS(eprofile->usage_rules, src->usage_rules);
+	}
+	if (rc == 0) {
+		rc = DUP_VARS(eprofile->confidence, src->confidence);
+	}
+	if (rc != 0) {
+		ao2_ref(eprofile, -1);
+		return NULL;
+	}
+
+
+	return eprofile;
+}
+
+struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_profile(struct ast_geoloc_profile *profile)
+{
+	struct ast_geoloc_eprofile *eprofile;
+	const char *profile_id;
+	int rc = 0;
+
+	if (!profile) {
+		return NULL;
+	}
+
+	profile_id = ast_sorcery_object_get_id(profile);
+
+	eprofile = ast_geoloc_eprofile_alloc(profile_id);
+	if (!eprofile) {
+		return NULL;
+	}
+
+	ao2_lock(profile);
+	eprofile->allow_routing_use = profile->allow_routing_use;
+	eprofile->pidf_element = profile->pidf_element;
+	eprofile->suppress_empty_ca_elements = profile->suppress_empty_ca_elements;
+	eprofile->format = profile->format;
+
+
+	rc = ast_string_field_set(eprofile, location_reference, profile->location_reference);
+	if (rc == 0) {
+		ast_string_field_set(eprofile, notes, profile->notes);
+	}
+	if (rc == 0) {
+		ast_string_field_set(eprofile, method, profile->method);
+	}
+	if (rc == 0) {
+		ast_string_field_set(eprofile, location_source, profile->location_source);
+	}
+	if (rc == 0) {
+		rc = DUP_VARS(eprofile->location_info, profile->location_info);
+	}
+	if (rc == 0) {
+		rc = DUP_VARS(eprofile->location_refinement, profile->location_refinement);
+	}
+	if (rc == 0) {
+		rc = DUP_VARS(eprofile->location_variables, profile->location_variables);
+	}
+	if (rc == 0) {
+		rc = DUP_VARS(eprofile->usage_rules, profile->usage_rules);
+	}
+	if (rc == 0) {
+		rc = DUP_VARS(eprofile->confidence, profile->confidence);
+	}
+	if (rc != 0) {
+		ao2_unlock(profile);
+		ao2_ref(eprofile, -1);
+		return NULL;
+	}
+
+	eprofile->precedence = profile->precedence;
+	ao2_unlock(profile);
+
+	if (ast_geoloc_eprofile_refresh_location(eprofile) != 0) {
+		ao2_ref(eprofile, -1);
+		return NULL;
+	}
+
+	return eprofile;
+}
+
+static int set_loc_src(struct ast_geoloc_eprofile *eprofile, const char *uri, const char *ref_str)
+{
+	char *local_uri = ast_strdupa(uri);
+	char *loc_src = NULL;
+
+	loc_src = strchr(local_uri, ';');
+	if (loc_src) {
+		*loc_src = '\0';
+		loc_src++;
+	}
+
+	if (!ast_strlen_zero(loc_src)) {
+		if (ast_begins_with(loc_src, "loc-src=")) {
+			struct ast_sockaddr loc_source_addr;
+			int rc = 0;
+			loc_src += 8;
+			rc = ast_sockaddr_parse(&loc_source_addr, loc_src, PARSE_PORT_FORBID);
+			if (rc == 1) {
+				ast_log(LOG_WARNING, "%s: URI '%s' has an invalid 'loc-src' parameter."
+					" RFC8787 states that IP addresses MUST be dropped.\n",
+					ref_str, uri);
+				return -1;
+			} else {
+				ast_string_field_set(eprofile, location_source, loc_src);
+			}
+		}
+	}
+	return 0;
+}
+
+struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_uri(const char *uri,
+	const char *ref_str)
+{
+	struct ast_geoloc_eprofile *eprofile = NULL;
+	char *ra = NULL;
+	char *local_uri;
+
+	if (ast_strlen_zero(uri)) {
+		return NULL;
+	}
+	local_uri = ast_strdupa(uri);
+
+	if (local_uri[0] == '<') {
+		local_uri++;
+	}
+	ra = strchr(local_uri, '>');
+	if (ra) {
+		*ra = '\0';
+	}
+
+	ast_strip(local_uri);
+
+	eprofile = ast_geoloc_eprofile_alloc(local_uri);
+	if (!eprofile) {
+		return NULL;
+	}
+
+	set_loc_src(eprofile, uri, ref_str);
+
+	eprofile->format = AST_GEOLOC_FORMAT_URI;
+	eprofile->location_info = ast_variable_new("URI", local_uri, "");
+
+	return eprofile;
+}
+
+struct ast_variable *geoloc_eprofile_resolve_varlist(struct ast_variable *source,
+	struct ast_variable *variables, struct ast_channel *chan)
+{
+	struct ast_variable *dest = NULL;
+	struct ast_variable *var = NULL;
+	struct varshead *vh = NULL;
+	struct ast_str *buf = ast_str_alloca(256);
+
+	if (!source || !chan) {
+		return NULL;
+	}
+
+	/*
+	 * ast_str_substitute_variables does only minimal recursive resolution so we need to
+	 * pre-resolve each variable in the "variables" list, then use that result to
+	 * do the final pass on the "source" variable list.
+	 */
+	if (variables) {
+		var = variables;
+		vh = ast_var_list_create();
+		if (!vh) {
+			return NULL;
+		}
+		for ( ; var; var = var->next) {
+			ast_str_substitute_variables_full2(&buf, 0, chan, vh, var->value, NULL, 1);
+			AST_VAR_LIST_INSERT_TAIL(vh, ast_var_assign(var->name, ast_str_buffer(buf)));
+			ast_str_reset(buf);
+		}
+	}
+
+	var = source;
+	for ( ; var; var = var->next) {
+		struct ast_variable *newvar = NULL;
+		ast_str_substitute_variables_full2(&buf, 0, chan, vh, var->value, NULL, 1);
+		newvar = ast_variable_new(var->name, ast_str_buffer(buf), "");
+		if (!newvar) {
+			ast_variables_destroy(dest);
+			ast_var_list_destroy(vh);
+			return NULL;
+		}
+		ast_variable_list_append(&dest, newvar);
+		ast_str_reset(buf);
+	}
+	ast_var_list_destroy(vh);
+
+	return dest;
+}
+
+
+const char *ast_geoloc_eprofile_to_uri(struct ast_geoloc_eprofile *eprofile,
+	struct ast_channel *chan, struct ast_str **buf, const char *ref_str)
+{
+	const char *uri = NULL;
+	struct ast_variable *resolved = NULL;
+	char *result;
+	int we_created_buf = 0;
+
+	if (!eprofile || !buf || !chan) {
+		return NULL;
+	}
+
+	if (eprofile->format != AST_GEOLOC_FORMAT_URI) {
+		ast_log(LOG_ERROR, "%s: '%s' is not a URI profile.  It's '%s'\n",
+			ref_str, eprofile->id, ast_geoloc_format_to_name(eprofile->format));
+		return NULL;
+	}
+
+	resolved = geoloc_eprofile_resolve_varlist(eprofile->effective_location,
+		eprofile->location_variables, chan);
+	if (!resolved) {
+		return NULL;
+	}
+
+	uri = ast_variable_find_in_list(resolved, "URI");
+	result = uri ? ast_strdupa(uri) : NULL;
+	ast_variables_destroy(resolved);
+
+	if (ast_strlen_zero(result)) {
+		ast_log(LOG_ERROR, "%s: '%s' is a URI profile but had no, or an empty, 'URI' entry in location_info\n",
+			ref_str, eprofile->id);
+		return NULL;
+	}
+
+	if (!*buf) {
+		*buf = ast_str_create(256);
+		if (!*buf) {
+			return NULL;
+		}
+		we_created_buf = 1;
+	}
+
+	if (ast_str_append(buf, 0, "%s", result) <= 0) {
+		if (we_created_buf) {
+			ast_free(*buf);
+			*buf = NULL;
+			return NULL;
+		}
+	}
+
+	return ast_str_buffer(*buf);
+}
+
+static struct ast_variable *var_list_from_node(struct ast_xml_node *node,
+	const char *ref_str)
+{
+	struct ast_variable *list = NULL;
+	struct ast_xml_node *container;
+	struct ast_xml_node *child;
+	struct ast_variable *var;
+	SCOPE_ENTER(3, "%s\n", ref_str);
+
+	container = ast_xml_node_get_children(node);
+	for (child = container; child; child = ast_xml_node_get_next(child)) {
+		const char *name = ast_xml_node_get_name(child);
+		const char *value = ast_xml_get_text(child);
+		const char *uom = ast_xml_get_attribute(child, "uom");
+
+		if (uom) {
+			/* '20 radians\0' */
+			char newval[strlen(value) + 1 + strlen(uom) + 1];
+			sprintf(newval, "%s %s", value, uom);
+			var = ast_variable_new(name, newval, "");
+		} else {
+			var = ast_variable_new(name, value, "");
+		}
+
+		ast_xml_free_text(value);
+		ast_xml_free_attr(uom);
+
+		if (!var) {
+			ast_variables_destroy(list);
+			SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
+		}
+		ast_variable_list_append(&list, var);
+	}
+
+	if (TRACE_ATLEAST(5)) {
+		struct ast_str *buf = NULL;
+		ast_variable_list_join(list, ", ", "=", "\"", &buf);
+		ast_trace(5, "%s: Result: %s\n", ref_str, ast_str_buffer(buf));
+		ast_free(buf);
+	}
+
+	SCOPE_EXIT_RTN_VALUE(list, "%s: Done\n", ref_str);
+}
+
+static struct ast_variable *var_list_from_loc_info(struct ast_xml_node *locinfo,
+	enum ast_geoloc_format format, const char *ref_str)
+{
+	struct ast_variable *list = NULL;
+	struct ast_variable *locinfo_list = NULL;
+	struct ast_xml_node *container;
+	struct ast_variable *var = NULL;
+	const char *attr;
+	SCOPE_ENTER(3, "%s\n", ref_str);
+
+	container = ast_xml_node_get_children(locinfo);
+	if (format == AST_GEOLOC_FORMAT_CIVIC_ADDRESS) {
+		attr = ast_xml_get_attribute(container, "lang");
+		if (attr) {
+			var = ast_variable_new("lang", attr, "");
+			ast_xml_free_attr(attr);
+			if (!var) {
+				SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
+			}
+			ast_variable_list_append(&list, var);
+		}
+	} else {
+		var = ast_variable_new("shape", ast_xml_node_get_name(container), "");
+		if (!var) {
+			SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
+		}
+		ast_variable_list_append(&list, var);
+
+		attr = ast_xml_get_attribute(container, "srsName");
+		var = ast_variable_new("crs", attr, "");
+		ast_xml_free_attr(attr);
+		if (!var) {
+			ast_variables_destroy(list);
+			SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
+		}
+		ast_variable_list_append(&list, var);
+	}
+
+	locinfo_list = var_list_from_node(container, ref_str);
+	if (locinfo_list == NULL) {
+		ast_log(LOG_WARNING, "%s: There were no elements in the location info\n", ref_str);
+		SCOPE_EXIT_RTN_VALUE(list, "%s: There were no elements in the location info\n", ref_str);
+	}
+	ast_variable_list_append(&list, locinfo_list);
+
+	if (TRACE_ATLEAST(5)) {
+		struct ast_str *buf = NULL;
+		ast_variable_list_join(list, ", ", "=", "\"", &buf);
+		ast_trace(5, "%s: Result: %s\n", ref_str, ast_str_buffer(buf));
+		ast_free(buf);
+	}
+
+	SCOPE_EXIT_RTN_VALUE(list, "%s: Done\n", ref_str);
+}
+
+static struct ast_variable *var_list_from_confidence(struct ast_xml_node *confidence,
+	const char *ref_str)
+{
+	struct ast_variable *list = NULL;
+	struct ast_variable *var;
+	const char *pdf;
+	const char *value;
+	SCOPE_ENTER(3, "%s\n", ref_str);
+
+	if (!confidence) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: No confidence\n", ref_str);
+	}
+
+	pdf = ast_xml_get_attribute(confidence, "pdf");
+	var = ast_variable_new("pdf", S_OR(pdf, "unknown"), "");
+	ast_xml_free_attr(pdf);
+	if (!var) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
+	}
+	ast_variable_list_append(&list, var);
+
+	value = ast_xml_get_text(confidence);
+	var = ast_variable_new("value", S_OR(value, "95"), "");
+	ast_xml_free_text(value);
+	if (!var) {
+		ast_variables_destroy(list);
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
+	}
+	ast_variable_list_append(&list, var);
+
+	if (TRACE_ATLEAST(5)) {
+		struct ast_str *buf = NULL;
+		ast_variable_list_join(list, ", ", "=", "\"", &buf);
+		ast_trace(5, "%s: Result: %s\n", ref_str, ast_str_buffer(buf));
+		ast_free(buf);
+	}
+
+	SCOPE_EXIT_RTN_VALUE(list, "%s: Done\n", ref_str);
+}
+
+static struct ast_geoloc_eprofile *geoloc_eprofile_create_from_xslt_result(
+	struct ast_xml_doc *result_doc, const char *ref_str)
+{
+	struct ast_geoloc_eprofile *eprofile;
+	/*
+	 * None of the ast_xml_nodes needs to be freed
+	 * because they're just pointers into result_doc.
+	 */
+	struct ast_xml_node *presence = NULL;
+	struct ast_xml_node *pidf_element = NULL;
+	struct ast_xml_node *location_info = NULL;
+	struct ast_xml_node *confidence = NULL;
+	struct ast_xml_node *usage_rules = NULL;
+	struct ast_xml_node *method = NULL;
+	struct ast_xml_node *note_well = NULL;
+	/*
+	 * Like nodes, names of nodes are just
+	 * pointers into result_doc and don't need to be freed.
+	 */
+	const char *pidf_element_str;
+	/*
+	 * Attributes and element text however are allocated on the fly
+	 * so they DO need to be freed after use.
+	 */
+	const char *id = NULL;
+	const char *format_str = NULL;
+	const char *method_str = NULL;
+	const char *note_well_str = NULL;
+
+	SCOPE_ENTER(3, "%s\n", ref_str);
+
+	if (!result_doc) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: result_doc was NULL", ref_str);
+	}
+
+	if (TRACE_ATLEAST(5)) {
+		char *doc_str = NULL;
+		int doc_len = 0;
+
+		ast_xml_doc_dump_memory(result_doc, &doc_str, &doc_len);
+		ast_trace(5, "xslt result doc len: %d\n%s\n", doc_len, doc_len ? doc_str : "<empty>");
+		ast_xml_free_text(doc_str);
+	}
+
+	presence = ast_xml_get_root(result_doc);
+	if (!presence) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Can't find 'presence' root element\n",
+			ref_str);
+	}
+
+	pidf_element = ast_xml_node_get_children(presence);
+	if (!pidf_element) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Can't find a device, tuple or person element\n",
+			ref_str);
+	}
+
+	id = ast_xml_get_attribute(pidf_element, "id");
+	if (ast_strlen_zero(id)) {
+		ast_xml_free_attr(id);
+		id = ast_xml_get_attribute(presence, "entity");
+	}
+
+	if (ast_strlen_zero(id)) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Unable to find 'id' attribute\n", ref_str);
+	}
+
+	eprofile = ast_geoloc_eprofile_alloc(id);
+	ast_xml_free_attr(id);
+	if (!eprofile) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Allocation failure\n", ref_str);
+	}
+
+	location_info = ast_xml_find_child_element(pidf_element, "location-info", NULL, NULL);
+	if (!location_info) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Can't find a location-info element\n",
+			ref_str);
+	}
+
+	format_str = ast_xml_get_attribute(location_info, "format");
+	if (ast_strlen_zero(format_str)) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Unable to find 'format' attribute\n", ref_str);
+	}
+
+	eprofile->format = AST_GEOLOC_FORMAT_NONE;
+	if (strcasecmp(format_str, "gml") == 0) {
+		eprofile->format = AST_GEOLOC_FORMAT_GML;
+	} else if (strcasecmp(format_str, "civicAddress") == 0) {
+		eprofile->format = AST_GEOLOC_FORMAT_CIVIC_ADDRESS;
+	}
+
+	if (eprofile->format == AST_GEOLOC_FORMAT_NONE) {
+		char *dup_format_str = ast_strdupa(format_str);
+		ast_xml_free_attr(format_str);
+		ao2_ref(eprofile, -1);
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unknown format '%s'\n", ref_str, dup_format_str);
+	}
+	ast_xml_free_attr(format_str);
+
+	pidf_element_str = ast_xml_node_get_name(pidf_element);
+	eprofile->pidf_element = ast_geoloc_pidf_element_str_to_enum(pidf_element_str);
+
+	eprofile->location_info = var_list_from_loc_info(location_info, eprofile->format, ref_str);
+	if (!eprofile->location_info) {
+		ao2_ref(eprofile, -1);
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR,
+			"%s: Unable to create location variables\n", ref_str);
+	}
+
+	/*
+	 * The function calls that follow are all NULL tolerant
+	 * so no need for explicit error checking.
+	 */
+	usage_rules = ast_xml_find_child_element(pidf_element, "usage-rules", NULL, NULL);
+	eprofile->usage_rules = var_list_from_node(usage_rules, ref_str);
+	confidence = ast_xml_find_child_element(location_info, "confidence", NULL, NULL);
+	eprofile->confidence = var_list_from_confidence(confidence, ref_str);
+
+	method = ast_xml_find_child_element(pidf_element, "method", NULL, NULL);
+	method_str = ast_xml_get_text(method);
+	ast_string_field_set(eprofile, method, method_str);
+	ast_xml_free_text(method_str);
+
+	note_well = ast_xml_find_child_element(pidf_element, "note-well", NULL, NULL);
+	note_well_str = ast_xml_get_text(note_well);
+	ast_string_field_set(eprofile, notes, note_well_str);
+	ast_xml_free_text(note_well_str);
+
+	SCOPE_EXIT_RTN_VALUE(eprofile, "%s: Done.\n", ref_str);
+}
+
+static int is_pidf_lo(struct ast_xml_doc *result_doc)
+{
+	struct ast_xml_node *presence;
+	struct ast_xml_node *pidf_element;
+	struct ast_xml_node *location_info;
+	const char *pidf_element_name;
+
+	if (!result_doc) {
+		return 0;
+	}
+	presence = ast_xml_get_root(result_doc);
+	if (!presence || !ast_strings_equal("presence", ast_xml_node_get_name(presence))) {
+		return 0;
+	}
+
+	pidf_element = ast_xml_node_get_children(presence);
+	if (!pidf_element) {
+		return 0;
+	}
+	pidf_element_name = ast_xml_node_get_name(pidf_element);
+	if (!ast_strings_equal(pidf_element_name, "device") &&
+		!ast_strings_equal(pidf_element_name, "tuple") &&
+		!ast_strings_equal(pidf_element_name, "person")) {
+		return 0;
+	}
+
+	location_info = ast_xml_find_child_element(pidf_element, "location-info", NULL, NULL);
+	if (!location_info) {
+		return 0;
+	}
+
+	return 1;
+}
+
+struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_pidf(
+	struct ast_xml_doc *pidf_xmldoc, const char *geoloc_uri, const char *ref_str)
+{
+	struct ast_xml_doc *result_doc = NULL;
+	struct ast_geoloc_eprofile *eprofile = NULL;
+
+	SCOPE_ENTER(3, "%s\n", ref_str);
+
+	result_doc = ast_xslt_apply(pidf_to_eprofile_xslt, pidf_xmldoc, NULL);
+	if (!is_pidf_lo(result_doc)) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Not a PIDF-LO.  Skipping.\n", ref_str);
+	}
+
+	/*
+	 * The document returned from the stylesheet application looks like this...
+	 * <presence id="presence-entity">
+	 *     <tuple id="element-id">
+	 *         <location-info format="gml">shape="Ellipsoid", crs="3d", ...</location-info>
+	 *         <usage-rules>retransmission-allowed="no", retention-expiry="2010-11-14T20:00:00Z"</usage-rules>
+	 *         <method>Hybrid_A-GPS</method>
+	 *     </tuple>
+	 *  </presence>
+	 *
+	 * Regardless of whether the pidf-element was tuple, device or person and whether
+	 * the format is gml or civicAddress, the presence, pidf-element and location-info
+	 * elements should be there.
+	 *
+	 * The confidence, usage-rules and note-well elements are optional.
+	 */
+
+	if (TRACE_ATLEAST(5)) {
+		char *doc_str = NULL;
+		int doc_len = 0;
+
+		ast_xml_doc_dump_memory(result_doc, &doc_str, &doc_len);
+		ast_trace(5, "Intermediate doc len: %d\n%s\n", doc_len, doc_len ? doc_str : "<empty>");
+		ast_xml_free_text(doc_str);
+		doc_str = NULL;
+		doc_len = 0;
+	}
+
+	eprofile = geoloc_eprofile_create_from_xslt_result(result_doc, ref_str);
+	ast_xml_close(result_doc);
+
+	if (eprofile && geoloc_uri) {
+		set_loc_src(eprofile, geoloc_uri, ref_str);
+	}
+
+	SCOPE_EXIT_RTN_VALUE(eprofile, "%s: Done.\n", ref_str);
+}
+
+/*!
+ * \internal
+ * \brief Create an common, intermediate XML document to pass to the outgoing XSLT process
+ *
+ * \param element_name The name of the top-level XML element to create
+ * \param eprofile     The eprofile
+ * \param chan         The channel to resolve variables against
+ * \param ref_string   A reference string for error messages
+ * \return             An XML doc
+ *
+ * \note Given that the document is simple and static, it was easier to just
+ * create the elements in a string buffer and call ast_xml_read_memory()
+ * at the end instead of creating
+ */
+static struct ast_xml_node *geoloc_eprofile_to_intermediate(const char *element_name, struct ast_geoloc_eprofile *eprofile,
+	struct ast_channel *chan, const char *ref_string)
+{
+	struct ast_variable *resolved_location = NULL;
+	struct ast_variable *resolved_usage = NULL;
+	struct ast_variable *var = NULL;
+	RAII_VAR(struct ast_xml_node *, pidf_node, NULL, ast_xml_free_node);
+	struct ast_xml_node *rtn_pidf_node;
+	struct ast_xml_node *loc_node;
+	struct ast_xml_node *confidence_node;
+	struct ast_xml_node *info_node;
+	struct ast_xml_node *rules_node;
+	struct ast_xml_node *method_node;
+	struct ast_xml_node *notes_node;
+	struct ast_xml_node *timestamp_node;
+	struct timeval tv = ast_tvnow();
+	struct tm tm = { 0, };
+	char timestr[32] = { 0, };
+	int rc = 0;
+
+	SCOPE_ENTER(3, "%s\n", ref_string);
+
+	if (!eprofile || !chan) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Either or both eprofile or chan were NULL\n", ref_string);
+	}
+
+	pidf_node = ast_xml_new_node(element_name);
+	if (!pidf_node) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create '%s' XML node\n",
+			ref_string, element_name);
+	}
+
+	loc_node = ast_xml_new_child(pidf_node, "location-info");
+	if (!loc_node) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'location-info' XML node\n",
+			ref_string);
+	}
+	rc = ast_xml_set_attribute(loc_node, "format", ast_geoloc_format_to_name(eprofile->format));
+	if (rc != 0) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to set 'format' XML attribute\n", ref_string);
+	}
+
+	resolved_location = geoloc_eprofile_resolve_varlist(eprofile->effective_location,
+		eprofile->location_variables, chan);
+	if (eprofile->format == AST_GEOLOC_FORMAT_CIVIC_ADDRESS) {
+		info_node = geoloc_civicaddr_list_to_xml(resolved_location, ref_string);
+	} else {
+		info_node = geoloc_gml_list_to_xml(resolved_location, ref_string);
+	}
+	ast_variables_destroy(resolved_location);
+
+	if (!info_node) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create XML from '%s' list\n",
+			ref_string, ast_geoloc_format_to_name(eprofile->format));
+	}
+	if (!ast_xml_add_child(loc_node, info_node)) {
+		ast_xml_free_node(info_node);
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable add '%s' node to XML document\n",
+			ref_string, ast_geoloc_format_to_name(eprofile->format));
+	}
+
+	if (eprofile->confidence) {
+		const char *value = S_OR(ast_variable_find_in_list(eprofile->confidence, "value"), "95");
+		const char *pdf = S_OR(ast_variable_find_in_list(eprofile->confidence, "pdf"), "unknown");
+
+		confidence_node = ast_xml_new_child(loc_node, "confidence");
+		if (!confidence_node) {
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'confidence' XML node\n",
+				ref_string);
+		}
+		rc = ast_xml_set_attribute(confidence_node, "pdf", pdf);
+		if (rc != 0) {
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to set 'pdf' attribute on 'confidence' element\n", ref_string);
+		}
+
+		ast_xml_set_text(confidence_node, value);
+	}
+
+	rules_node = ast_xml_new_child(pidf_node, "usage-rules");
+	if (!rules_node) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'usage-rules' XML node\n",
+			ref_string);
+	}
+	resolved_usage = geoloc_eprofile_resolve_varlist(eprofile->usage_rules,
+		eprofile->location_variables, chan);
+	for (var = resolved_usage; var; var = var->next) {
+		struct ast_xml_node *ur = ast_xml_new_child(rules_node, var->name);
+		ast_xml_set_text(ur, var->value);
+	}
+	ast_variables_destroy(resolved_usage);
+
+	if (!ast_strlen_zero(eprofile->method)) {
+		method_node = ast_xml_new_child(pidf_node, "method");
+		if (!method_node) {
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'method' XML node\n",
+				ref_string);
+		}
+		ast_xml_set_text(method_node, eprofile->method);
+	};
+
+	if (!ast_strlen_zero(eprofile->notes)) {
+		notes_node = ast_xml_new_child(pidf_node, "note-well");
+		if (!notes_node) {
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'note-well' XML node\n",
+				ref_string);
+		}
+		ast_xml_set_text(notes_node, eprofile->notes);
+	};
+
+	gmtime_r(&tv.tv_sec, &tm);
+	strftime(timestr, sizeof(timestr), "%FT%TZ", &tm);
+	timestamp_node = ast_xml_new_child(pidf_node, "timestamp");
+	if (!timestamp_node) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'timestamp' XML node\n",
+			ref_string);
+	}
+	ast_xml_set_text(timestamp_node, timestr);
+
+	rtn_pidf_node = pidf_node;
+	pidf_node = NULL;
+	SCOPE_EXIT_RTN_VALUE(rtn_pidf_node, "%s: Done\n", ref_string);
+}
+
+#define CREATE_NODE_LIST(node) \
+	if (!node) { \
+		node = ast_xml_new_child(root_node, \
+			geoloc_pidf_element_to_name(eprofile->pidf_element)); \
+		if (!pidfs[eprofile->pidf_element]) { \
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create pidf '%s' XML node\n", \
+				ref_string, geoloc_pidf_element_to_name(eprofile->pidf_element)); \
+		} \
+	}
+
+const char *ast_geoloc_eprofiles_to_pidf(struct ast_datastore *ds,
+	struct ast_channel *chan, struct ast_str **buf, const char * ref_string)
+{
+	RAII_VAR(struct ast_xml_doc *, intermediate, NULL, ast_xml_close);
+	RAII_VAR(struct ast_xml_doc *, pidf_doc, NULL, ast_xml_close);
+	struct ast_xml_node *root_node;
+	struct ast_xml_node *pidfs[AST_PIDF_ELEMENT_LAST] = {NULL, };
+	struct ast_geoloc_eprofile *eprofile;
+	int eprofile_count = 0;
+	int i;
+	char *doc_str = NULL;
+	int doc_len = 0;
+	int rc = 0;
+	SCOPE_ENTER(3, "%s\n", ref_string);
+
+	if (!ds || !chan || !buf || !*buf || ast_strlen_zero(ref_string)) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: Either or both datastore or chan were NULL\n",
+			ref_string);
+	}
+
+	intermediate = ast_xml_new();
+	if (!intermediate) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create XML document\n", ref_string);
+	}
+	root_node = ast_xml_new_node("presence");
+	if (!root_node) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create root XML node\n", ref_string);
+	}
+	ast_xml_set_root(intermediate, root_node);
+
+	eprofile_count = ast_geoloc_datastore_size(ds);
+	for (i = 0; i < eprofile_count; i++) {
+		struct ast_xml_node *temp_node = NULL;
+		struct ast_xml_node *curr_loc = NULL;
+		struct ast_xml_node *new_loc = NULL;
+		struct ast_xml_node *new_loc_child = NULL;
+		struct ast_xml_node *new_loc_child_dup = NULL;
+		const char *entity = NULL;
+		int has_no_entity = 0;
+		eprofile = ast_geoloc_datastore_get_eprofile(ds, i);
+		if (eprofile->format == AST_GEOLOC_FORMAT_URI) {
+			continue;
+		}
+
+		entity = ast_xml_get_attribute(root_node, "entity");
+		has_no_entity = ast_strlen_zero(entity);
+		ast_xml_free_attr(entity);
+		if (has_no_entity) {
+			rc = ast_xml_set_attribute(root_node, "entity", eprofile->id);
+			if (rc != 0) {
+				SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to set 'entity' XML attribute\n", ref_string);
+			}
+		}
+
+		temp_node = geoloc_eprofile_to_intermediate(ast_geoloc_pidf_element_to_name(eprofile->pidf_element),
+			eprofile, chan, ref_string);
+		if (!temp_node) {
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create temp_node\n", ref_string);
+		}
+
+		if (!pidfs[eprofile->pidf_element]) {
+			pidfs[eprofile->pidf_element] = temp_node;
+			ast_xml_add_child(root_node, temp_node);
+			continue;
+		}
+
+		curr_loc = ast_xml_find_child_element(pidfs[eprofile->pidf_element], "location-info", NULL, NULL);
+		new_loc = ast_xml_find_child_element(temp_node, "location-info", NULL, NULL);
+		new_loc_child = ast_xml_node_get_children(new_loc);
+		new_loc_child_dup = ast_xml_copy_node_list(new_loc_child);
+		ast_xml_add_child_list(curr_loc, new_loc_child_dup);
+
+		ast_xml_free_node(temp_node);
+	}
+
+	if (TRACE_ATLEAST(5)) {
+		ast_xml_doc_dump_memory(intermediate, &doc_str, &doc_len);
+		ast_trace(5, "Intermediate doc len: %d\n%s\n", doc_len, doc_len ? doc_str : "<empty>");
+		ast_xml_free_text(doc_str);
+		doc_str = NULL;
+		doc_len = 0;
+	}
+
+	pidf_doc = ast_xslt_apply(eprofile_to_pidf_xslt, intermediate, NULL);
+	if (!pidf_doc) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create final PIDF-LO doc from intermediate docs\n",
+			ref_string);
+	}
+
+	ast_xml_doc_dump_memory(pidf_doc, &doc_str, &doc_len);
+	if (doc_len == 0 || !doc_str) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to dump final PIDF-LO doc to string\n",
+			ref_string);
+	}
+
+	rc = ast_str_set(buf, 0, "%s", doc_str);
+	ast_xml_free_text(doc_str);
+	if (rc <= 0) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to extend buffer (%d)\n",
+			ref_string, rc);
+	}
+
+	ast_trace(5, "Final doc:\n%s\n", ast_str_buffer(*buf));
+
+	SCOPE_EXIT_RTN_VALUE(ast_str_buffer(*buf), "%s: Done\n", ref_string);
+}
+
+const char *ast_geoloc_eprofile_to_pidf(struct ast_geoloc_eprofile *eprofile,
+	struct ast_channel *chan, struct ast_str **buf, const char * ref_string)
+{
+	RAII_VAR(struct ast_xml_doc *, intermediate, NULL, ast_xml_close);
+	RAII_VAR(struct ast_xml_doc *, pidf_doc, NULL, ast_xml_close);
+	struct ast_xml_node *root_node;
+	char *doc_str = NULL;
+	int doc_len;
+	int rc = 0;
+	struct ast_xml_node *temp_node = NULL;
+	const char *entity = NULL;
+	int has_no_entity = 0;
+	const char *params[] = { "suppress_empty_ca_elements", "false()", NULL };
+
+	SCOPE_ENTER(3, "%s\n", ref_string);
+
+	if (!eprofile || !chan || !buf || !*buf || ast_strlen_zero(ref_string)) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: One of eprofile, chan or buf was NULL\n",
+			ref_string);
+	}
+
+	if (eprofile->format == AST_GEOLOC_FORMAT_URI) {
+		SCOPE_EXIT_RTN_VALUE(NULL, "%s: eprofile '%s' was a URI format\n",
+			ref_string, eprofile->id);
+	}
+
+	intermediate = ast_xml_new();
+	if (!intermediate) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create XML document\n", ref_string);
+	}
+	root_node = ast_xml_new_node("presence");
+	if (!root_node) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create root XML node\n", ref_string);
+	}
+	ast_xml_set_root(intermediate, root_node);
+
+	entity = ast_xml_get_attribute(root_node, "entity");
+	has_no_entity = ast_strlen_zero(entity);
+	ast_xml_free_attr(entity);
+	if (has_no_entity) {
+		rc = ast_xml_set_attribute(root_node, "entity", eprofile->id);
+		if (rc != 0) {
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to set 'entity' XML attribute\n", ref_string);
+		}
+	}
+
+	temp_node = geoloc_eprofile_to_intermediate(
+		ast_geoloc_pidf_element_to_name(eprofile->pidf_element), eprofile, chan, ref_string);
+	if (!temp_node) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create temp_node for eprofile '%s'\n",
+			ref_string, eprofile->id);
+	}
+
+	ast_xml_add_child(root_node, temp_node);
+
+	if (TRACE_ATLEAST(5)) {
+		ast_xml_doc_dump_memory(intermediate, &doc_str, &doc_len);
+		ast_trace(5, "Intermediate doc len: %d\n%s\n", doc_len, doc_len ? doc_str : "<empty>");
+		ast_xml_free_text(doc_str);
+		doc_str = NULL;
+		doc_len = 0;
+	}
+
+	if (eprofile->suppress_empty_ca_elements) {
+		params[1] = "true()";
+	}
+	pidf_doc = ast_xslt_apply(eprofile_to_pidf_xslt, intermediate, params);
+	if (!pidf_doc) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create final PIDF-LO doc from intermediate doc\n",
+			ref_string);
+	}
+
+	ast_xml_doc_dump_memory(pidf_doc, &doc_str, &doc_len);
+	if (doc_len == 0 || !doc_str) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to dump final PIDF-LO doc to string\n",
+			ref_string);
+	}
+
+	rc = ast_str_set(buf, 0, "%s", doc_str);
+	ast_xml_free_text(doc_str);
+	if (rc <= 0) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to extend buffer (%d)\n",
+			ref_string, rc);
+	}
+
+	ast_trace(5, "Final doc:\n%s\n", ast_str_buffer(*buf));
+
+	SCOPE_EXIT_RTN_VALUE(ast_str_buffer(*buf), "%s: Done\n", ref_string);
+}
+
+#ifdef TEST_FRAMEWORK
+static void load_tests(void);
+static void unload_tests(void);
+#else
+static void load_tests(void) {}
+static void unload_tests(void) {}
+#endif
+
+
+int geoloc_eprofile_unload(void)
+{
+	unload_tests();
+	if (pidf_to_eprofile_xslt) {
+		ast_xslt_close(pidf_to_eprofile_xslt);
+	}
+
+	if (eprofile_to_pidf_xslt) {
+		ast_xslt_close(eprofile_to_pidf_xslt);
+	}
+
+	if (geoloc_sorcery) {
+		ast_sorcery_unref(geoloc_sorcery);
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_eprofile_load(void)
+{
+	pidf_to_eprofile_xslt_size =
+		(_binary_res_geolocation_pidf_to_eprofile_xslt_end - _binary_res_geolocation_pidf_to_eprofile_xslt_start);
+
+	pidf_lo_test_xml_size =
+		(_binary_res_geolocation_pidf_lo_test_xml_end - _binary_res_geolocation_pidf_lo_test_xml_start);
+
+	pidf_to_eprofile_xslt = ast_xslt_read_memory(
+		(char *)_binary_res_geolocation_pidf_to_eprofile_xslt_start, pidf_to_eprofile_xslt_size);
+	if (!pidf_to_eprofile_xslt) {
+		ast_log(LOG_ERROR, "Unable to read pidf_to_eprofile_xslt from memory\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	eprofile_to_pidf_xslt_size =
+		(_binary_res_geolocation_eprofile_to_pidf_xslt_end - _binary_res_geolocation_eprofile_to_pidf_xslt_start);
+
+	eprofile_to_pidf_xslt = ast_xslt_read_memory(
+		(char *)_binary_res_geolocation_eprofile_to_pidf_xslt_start, eprofile_to_pidf_xslt_size);
+	if (!eprofile_to_pidf_xslt) {
+		ast_log(LOG_ERROR, "Unable to read eprofile_to_pidf_xslt from memory\n");
+//		geoloc_eprofile_unload();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	geoloc_sorcery = geoloc_get_sorcery();
+
+	load_tests();
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_eprofile_reload(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+
+#ifdef TEST_FRAMEWORK
+#include "asterisk/test.h"
+
+AST_TEST_DEFINE(test_create_from_uri)
+{
+
+	RAII_VAR(struct ast_geoloc_eprofile *, eprofile,  NULL, ao2_cleanup);
+	const char *uri = NULL;
+	int rc = AST_TEST_PASS;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "create_from_uri";
+		info->category = "/geoloc/";
+		info->summary = "Test create from uri";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	eprofile = ast_geoloc_eprofile_create_from_uri("http://some_uri&a=b", __func__);
+	ast_test_validate(test, eprofile != NULL);
+	ast_test_validate(test, eprofile->format == AST_GEOLOC_FORMAT_URI);
+	ast_test_validate(test, eprofile->location_info != NULL);
+	uri = ast_variable_find_in_list(eprofile->location_info, "URI");
+	ast_test_validate(test, uri != NULL);
+	ast_test_validate(test, strcmp(uri, "http://some_uri&a=b") == 0);
+
+	return rc;
+}
+
+static enum ast_test_result_state validate_eprofile(struct ast_test *test,
+	struct ast_xml_doc * pidf_xmldoc,
+	const char *path,
+	const char *id,
+	enum ast_geoloc_pidf_element pidf_element,
+	enum ast_geoloc_format format,
+	const char *method,
+	const char *location,
+	const char *usage
+	)
+{
+	RAII_VAR(struct ast_str *, str, NULL, ast_free);
+	RAII_VAR(struct ast_geoloc_eprofile *, eprofile,  NULL, ao2_cleanup);
+	RAII_VAR(struct ast_xml_doc *, result_doc, NULL, ast_xml_close);
+
+	if (!ast_strlen_zero(path)) {
+		result_doc = ast_xslt_apply(pidf_to_eprofile_xslt, pidf_xmldoc, NULL);
+		ast_test_validate(test, (result_doc && ast_xml_node_get_children((struct ast_xml_node *)result_doc)));
+
+		eprofile = geoloc_eprofile_create_from_xslt_result(result_doc, "test_create_from_xslt");
+	} else {
+		eprofile = ast_geoloc_eprofile_create_from_pidf(pidf_xmldoc, NULL, "test_create_from_pidf");
+	}
+
+	ast_test_validate(test, eprofile != NULL);
+	ast_test_status_update(test, "ID: '%s'  pidf_element: '%s'  format: '%s'  method: '%s'\n", eprofile->id,
+		ast_geoloc_pidf_element_to_name(eprofile->pidf_element),
+		ast_geoloc_format_to_name(eprofile->format),
+		eprofile->method);
+
+	ast_test_validate(test, ast_strings_equal(eprofile->id, id));
+	ast_test_validate(test, eprofile->pidf_element == pidf_element);
+	ast_test_validate(test, eprofile->format == format);
+	ast_test_validate(test, ast_strings_equal(eprofile->method, method));
+
+	str = ast_variable_list_join(eprofile->location_info, ",", "=", NULL, NULL);
+	ast_test_validate(test, str != NULL);
+	ast_test_status_update(test, "location_vars expected: %s\n", location);
+	ast_test_status_update(test, "location_vars received: %s\n", ast_str_buffer(str));
+	ast_test_validate(test, ast_strings_equal(ast_str_buffer(str), location));
+	ast_free(str);
+
+	str = ast_variable_list_join(eprofile->usage_rules, ",", "=", "'", NULL);
+	ast_test_validate(test, str != NULL);
+	ast_test_status_update(test, "usage_rules expected: %s\n", usage);
+	ast_test_status_update(test, "usage_rules received: %s\n", ast_str_buffer(str));
+	ast_test_validate(test, ast_strings_equal(ast_str_buffer(str), usage));
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(test_create_from_pidf)
+{
+
+	RAII_VAR(struct ast_xml_doc *, pidf_xmldoc, NULL, ast_xml_close);
+	enum ast_test_result_state res = AST_TEST_PASS;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "create_from_pidf";
+		info->category = "/geoloc/";
+		info->summary = "Test create from pidf scenarios";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	pidf_xmldoc = ast_xml_read_memory((char *)_binary_res_geolocation_pidf_lo_test_xml_start, pidf_lo_test_xml_size);
+	ast_test_validate(test, pidf_xmldoc != NULL);
+
+	res = validate_eprofile(test, pidf_xmldoc,
+		NULL,
+		"point-2d",
+		AST_PIDF_ELEMENT_TUPLE,
+		AST_GEOLOC_FORMAT_GML,
+		"Manual",
+		"shape=Point,crs=2d,pos=-34.410649 150.87651",
+		"retransmission-allowed='no',retention-expiry='2010-11-14T20:00:00Z'"
+		);
+	ast_test_validate(test, res == AST_TEST_PASS);
+
+	return res;
+}
+
+static void load_tests(void) {
+	AST_TEST_REGISTER(test_create_from_uri);
+	AST_TEST_REGISTER(test_create_from_pidf);
+}
+static void unload_tests(void) {
+	AST_TEST_UNREGISTER(test_create_from_uri);
+	AST_TEST_UNREGISTER(test_create_from_pidf);
+}
+
+#endif
diff --git a/res/res_geolocation/geoloc_gml.c b/res/res_geolocation/geoloc_gml.c
new file mode 100644
index 0000000000000000000000000000000000000000..9a5942cb2561c06bdf49534856d02440af950dad
--- /dev/null
+++ b/res/res_geolocation/geoloc_gml.c
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#include "asterisk.h"
+#include "asterisk/config.h"
+#include "asterisk/cli.h"
+#include "asterisk/res_geolocation.h"
+#include "geoloc_private.h"
+
+
+#if 1 //not used yet.
+enum geoloc_shape_attrs {
+	GEOLOC_SHAPE_ATTR_POS = 0,
+	GEOLOC_SHAPE_ATTR_POS3D,
+	GEOLOC_SHAPE_ATTR_RADIUS,
+	GEOLOC_SHAPE_ATTR_SEMI_MAJOR_AXIS,
+	GEOLOC_SHAPE_ATTR_SEMI_MINOR_AXIS,
+	GEOLOC_SHAPE_ATTR_VERTICAL_AXIS,
+	GEOLOC_SHAPE_ATTR_HEIGHT,
+	GEOLOC_SHAPE_ATTR_ORIENTATION,
+	GEOLOC_SHAPE_ATTR_ORIENTATION_UOM,
+	GEOLOC_SHAPE_ATTR_INNER_RADIUS,
+	GEOLOC_SHAPE_ATTR_OUTER_RADIUS,
+	GEOLOC_SHAPE_ATTR_STARTING_ANGLE,
+	GEOLOC_SHAPE_ATTR_OPENING_ANGLE,
+	GEOLOC_SHAPE_ATTR_ANGLE_UOM,
+};
+
+struct geoloc_gml_attr_def {
+	enum geoloc_shape_attrs attr;
+	const char *name;
+	int (*validator)(const char *value);
+	int (*transformer)(struct ast_variable *value);
+};
+
+struct geoloc_gml_attr_def gml_attr_defs[] = {
+	{ GEOLOC_SHAPE_ATTR_POS, "pos", NULL, NULL},
+	{ GEOLOC_SHAPE_ATTR_POS3D,"pos3d", NULL, NULL},
+	{ GEOLOC_SHAPE_ATTR_RADIUS,"radius", NULL, NULL},
+	{ GEOLOC_SHAPE_ATTR_SEMI_MAJOR_AXIS,"semiMajorAxis", NULL, NULL},
+	{ GEOLOC_SHAPE_ATTR_SEMI_MINOR_AXIS,"semiMinorAxis", NULL, NULL},
+	{ GEOLOC_SHAPE_ATTR_VERTICAL_AXIS,"verticalAxis", NULL, NULL},
+	{ GEOLOC_SHAPE_ATTR_HEIGHT,"height", NULL, NULL},
+	{ GEOLOC_SHAPE_ATTR_ORIENTATION,"orientation", NULL, NULL},
+	{ GEOLOC_SHAPE_ATTR_ORIENTATION_UOM,"orientation_uom", NULL, NULL},
+	{ GEOLOC_SHAPE_ATTR_INNER_RADIUS,"innerRadius", NULL, NULL},
+	{ GEOLOC_SHAPE_ATTR_OUTER_RADIUS,"outerRadius", NULL, NULL},
+	{ GEOLOC_SHAPE_ATTR_STARTING_ANGLE,"startingAngle", NULL, NULL},
+	{ GEOLOC_SHAPE_ATTR_OPENING_ANGLE,"openingAngle", NULL, NULL},
+	{ GEOLOC_SHAPE_ATTR_ANGLE_UOM,"angle_uom", NULL, NULL},
+};
+#endif  //not used yet.
+
+struct geoloc_gml_attr {
+	const char *attribute;
+	int min_required;
+	int max_allowed;
+	int (*validator)(const char *value);
+};
+
+struct geoloc_gml_shape_def {
+	const char *shape_type;
+	struct geoloc_gml_attr required_attributes[8];
+};
+
+static int pos_validator(const char *value)
+{
+	float lat;
+	float lon;
+	return (sscanf(value, "%f %f", &lat, &lon) == 2);
+}
+
+static int pos3d_validator(const char *value)
+{
+	float lat;
+	float lon;
+	float alt;
+	return (sscanf(value, "%f %f %f", &lat, &lon, &alt) == 3);
+}
+
+static int float_validator(const char *value)
+{
+	float val;
+	return (sscanf(value, "%f", &val) == 1);
+}
+
+static int uom_validator(const char *value)
+{
+	return (ast_strings_equal(value, "degrees") || ast_strings_equal(value, "radians"));
+}
+
+
+static struct geoloc_gml_shape_def gml_shape_defs[8] = {
+	{ "Point", { {"pos", 1, 1, pos_validator}, {NULL, -1, -1} }},
+	{ "Polygon", { {"pos", 3, -1, pos_validator}, {NULL, -1, -1} }},
+	{ "Circle", { {"pos", 1, 1, pos_validator}, {"radius", 1, 1, float_validator},{NULL, -1, -1}}},
+	{ "Ellipse", { {"pos", 1, 1, pos_validator}, {"semiMajorAxis", 1, 1, float_validator},
+		{"semiMinorAxis", 1, 1, float_validator}, {"orientation", 1, 1, float_validator},
+		{"orientation_uom", 1, 1, uom_validator}, {NULL, -1, -1} }},
+	{ "ArcBand", { {"pos", 1, 1, pos_validator}, {"innerRadius", 1, 1, float_validator},
+		{"outerRadius", 1, 1, float_validator}, {"startAngle", 1, 1, float_validator},
+		{"startAngle_uom", 1, 1, uom_validator}, {"openingAngle", 1, 1, float_validator},
+		{"openingAngle_uom", 1, 1, uom_validator}, {NULL, -1, -1} }},
+	{ "Sphere", { {"pos3d", 1, 1, pos3d_validator}, {"radius", 1, 1, float_validator}, {NULL, -1, -1} }},
+	{ "Ellipse", { {"pos3d", 1, 1, pos3d_validator}, {"semiMajorAxis", 1, 1, float_validator},
+		{"semiMinorAxis", 1, 1, float_validator}, {"verticalAxis", 1, 1, float_validator},
+		{"orientation", 1, 1, float_validator}, {"orientation_uom", 1, 1, uom_validator}, {NULL, -1, -1} }},
+	{ "Prism", { {"pos3d", 3, -1, pos_validator}, {"height", 1, 1, float_validator}, {NULL, -1, -1} }},
+};
+
+enum ast_geoloc_validate_result ast_geoloc_gml_validate_varlist(const struct ast_variable *varlist,
+	const char **result)
+{
+	int def_index = -1;
+	const struct ast_variable *var;
+	int i;
+	const char *shape_type = ast_variable_find_in_list(varlist, "shape");
+
+	if (!shape_type) {
+		return AST_GEOLOC_VALIDATE_MISSING_SHAPE;
+	}
+
+	for (i = 0; i < ARRAY_LEN(gml_shape_defs); i++) {
+		if (ast_strings_equal(gml_shape_defs[i].shape_type, shape_type)) {
+			def_index = i;
+		}
+	}
+	if (def_index < 0) {
+		return AST_GEOLOC_VALIDATE_INVALID_SHAPE;
+	}
+
+	for (var = varlist; var; var = var->next) {
+		int vname_index = -1;
+		if (ast_strings_equal("shape", var->name)) {
+			continue;
+		}
+		for (i = 0; i < ARRAY_LEN(gml_shape_defs[def_index].required_attributes); i++) {
+			if (gml_shape_defs[def_index].required_attributes[i].attribute == NULL) {
+				break;
+			}
+			if (ast_strings_equal(gml_shape_defs[def_index].required_attributes[i].attribute, var->name)) {
+				vname_index = i;
+				break;
+			}
+		}
+		if (vname_index < 0) {
+			*result = var->name;
+			return AST_GEOLOC_VALIDATE_INVALID_VARNAME;
+		}
+		if (!gml_shape_defs[def_index].required_attributes[vname_index].validator(var->value)) {
+			*result = var->name;
+			return AST_GEOLOC_VALIDATE_INVALID_VALUE;
+		}
+	}
+
+	for (i = 0; i < ARRAY_LEN(gml_shape_defs[def_index].required_attributes); i++) {
+		int count = 0;
+		if (gml_shape_defs[def_index].required_attributes[i].attribute == NULL) {
+			break;
+		}
+
+		for (var = varlist; var; var = var->next) {
+			if (ast_strings_equal(gml_shape_defs[def_index].required_attributes[i].attribute, var->name)) {
+				count++;
+			}
+		}
+		if (count < gml_shape_defs[def_index].required_attributes[i].min_required) {
+			*result = gml_shape_defs[def_index].required_attributes[i].attribute;
+			return AST_GEOLOC_VALIDATE_NOT_ENOUGH_VARNAMES;
+		}
+		if (gml_shape_defs[def_index].required_attributes[i].max_allowed > 0 &&
+			count > gml_shape_defs[def_index].required_attributes[i].max_allowed) {
+			*result = gml_shape_defs[def_index].required_attributes[i].attribute;
+			return AST_GEOLOC_VALIDATE_TOO_MANY_VARNAMES;
+		}
+	}
+	return AST_GEOLOC_VALIDATE_SUCCESS;
+}
+
+static char *handle_gml_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	int i;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "geoloc show gml_shape_defs";
+		e->usage =
+			"Usage: geoloc show gml_shape_defs\n"
+			"       Show the GML Shape definitions.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	ast_cli(a->fd, "%-16s %-32s\n", "Shape", "Attributes name(min,max)");
+	ast_cli(a->fd, "================ ===============================\n");
+
+	for (i = 0; i < ARRAY_LEN(gml_shape_defs); i++) {
+		int j;
+		ast_cli(a->fd, "%-16s", gml_shape_defs[i].shape_type);
+		for (j = 0; j < ARRAY_LEN(gml_shape_defs[i].required_attributes); j++) {
+			if (gml_shape_defs[i].required_attributes[j].attribute == NULL) {
+				break;
+			}
+			if (gml_shape_defs[i].required_attributes[j].max_allowed >= 0) {
+				ast_cli(a->fd, " %s(%d,%d)", gml_shape_defs[i].required_attributes[j].attribute,
+					gml_shape_defs[i].required_attributes[j].min_required,
+					gml_shape_defs[i].required_attributes[j].max_allowed);
+			} else {
+				ast_cli(a->fd, " %s(%d,unl)", gml_shape_defs[i].required_attributes[j].attribute,
+					gml_shape_defs[i].required_attributes[j].min_required);
+			}
+		}
+		ast_cli(a->fd, "\n");
+	}
+	ast_cli(a->fd, "\n");
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry geoloc_gml_cli[] = {
+	AST_CLI_DEFINE(handle_gml_show, "Show the GML Shape definitions"),
+};
+
+struct ast_xml_node *geoloc_gml_list_to_xml(const struct ast_variable *resolved_location,
+	const char *ref_string)
+{
+	const char *shape;
+	char *crs;
+	struct ast_variable *var;
+	struct ast_xml_node *gml_node;
+	struct ast_xml_node *child_node;
+	int rc = 0;
+
+	SCOPE_ENTER(3, "%s", ref_string);
+
+	if (!resolved_location) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: resolved_location was NULL\n",
+			ref_string);
+	}
+
+	shape = ast_variable_find_in_list(resolved_location, "shape");
+	if (ast_strlen_zero(shape)) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: There's no 'shape' parameter\n",
+			ref_string);
+	}
+	crs = (char *)ast_variable_find_in_list(resolved_location, "crs");
+	if (ast_strlen_zero(crs)) {
+		crs = "2d";
+	}
+
+	gml_node = ast_xml_new_node(shape);
+	if (!gml_node) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create '%s' XML node\n", shape, ref_string);
+	}
+	rc = ast_xml_set_attribute(gml_node, "crs", crs);
+	if (rc != 0) {
+		ast_xml_free_node(gml_node);
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'crs' XML attribute\n", ref_string);
+	}
+
+	for (var = (struct ast_variable *)resolved_location; var; var = var->next) {
+		RAII_VAR(char *, value, NULL, ast_free);
+		char *uom = NULL;
+
+		if (ast_strings_equal(var->name, "shape") || ast_strings_equal(var->name, "crs")) {
+			continue;
+		}
+		value = ast_strdup(var->value);
+
+		if (ast_strings_equal(var->name, "orientation") || ast_strings_equal(var->name, "startAngle")
+			|| ast_strings_equal(var->name, "openingAngle")) {
+			char *a = NULL;
+			char *junk = NULL;
+			float angle;
+			uom = value;
+
+			/* 'a' should now be the angle and 'uom' should be the uom */
+			a = strsep(&uom, " ");
+			angle = strtof(a, &junk);
+			/*
+			 * strtof sets junk to the first non-valid character so if it's
+			 * not empty after the conversion, there were unrecognized
+			 * characters in the angle.  It'll point to the NULL terminator
+			 * if angle was completely converted.
+			 */
+			if (!ast_strlen_zero(junk)) {
+				ast_xml_free_node(gml_node);
+				SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: The angle portion of parameter '%s' ('%s') is malformed\n",
+					ref_string, var->name, var->value);
+			}
+
+			if (ast_strlen_zero(uom)) {
+				uom = "degrees";
+			}
+
+			if (ast_begins_with(uom, "deg")) {
+				if (angle > 360.0) {
+					ast_xml_free_node(gml_node);
+					SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Parameter '%s': '%s' is malformed. "
+						"Degrees can't be > 360.0\n",
+						ref_string, var->name, var->value);
+				}
+			} else if (ast_begins_with(uom, "rad")) {
+				if(angle > 100.0) {
+					ast_xml_free_node(gml_node);
+					SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Parameter '%s': '%s' is malformed. "
+						"Radians can't be  > 100.0\n",
+						ref_string, var->name, var->value);
+				}
+			} else {
+				ast_xml_free_node(gml_node);
+				SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Parameter '%s': '%s' is malformed. "
+					"The unit of measure must be 'deg[rees]' or 'rad[ians]'\n",
+					ref_string, var->name, var->value);
+			}
+		}
+
+		child_node = ast_xml_new_child(gml_node, var->name);
+		if (!child_node) {
+			ast_xml_free_node(gml_node);
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create '%s' XML node\n", var->name, ref_string);
+		}
+		if (!ast_strlen_zero(uom)) {
+			rc = ast_xml_set_attribute(child_node, "uom", uom);
+			if (rc != 0) {
+				ast_xml_free_node(gml_node);
+				SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create 'uom' XML attribute\n", ref_string);
+			}
+		}
+		ast_xml_set_text(child_node, value);
+	}
+
+	SCOPE_EXIT_RTN_VALUE(gml_node, "%s: Done\n", ref_string);
+}
+
+int geoloc_gml_unload(void)
+{
+	ast_cli_unregister_multiple(geoloc_gml_cli, ARRAY_LEN(geoloc_gml_cli));
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_gml_load(void)
+{
+	ast_cli_register_multiple(geoloc_gml_cli, ARRAY_LEN(geoloc_gml_cli));
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+int geoloc_gml_reload(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
diff --git a/res/res_geolocation/geoloc_private.h b/res/res_geolocation/geoloc_private.h
new file mode 100644
index 0000000000000000000000000000000000000000..0bd0797cb7dcb17ac249f075102fc6177ac3f417
--- /dev/null
+++ b/res/res_geolocation/geoloc_private.h
@@ -0,0 +1,162 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef GEOLOC_PRIVATE_H_
+#define GEOLOC_PRIVATE_H_
+
+#include "asterisk/module.h"
+#include "asterisk/config.h"
+#include "asterisk/sorcery.h"
+#include "asterisk/lock.h"
+#include "asterisk/res_geolocation.h"
+
+#define CONFIG_STR_TO_ENUM(_stem) \
+int ast_geoloc_ ## _stem ## _str_to_enum(const char *str) \
+{ \
+	int i; \
+	for (i = 0; i < ARRAY_LEN(_stem ## _names); i++) { \
+		if (ast_strings_equal(str, _stem ## _names[i])) { \
+			return i; \
+		} \
+	} \
+	return -1; \
+}
+
+#define CONFIG_ENUM_HANDLER(_object, _stem) \
+static int  _object ## _ ## _stem ## _handler(const struct aco_option *opt, struct ast_variable *var, void *obj) \
+{ \
+	struct ast_geoloc_ ## _object *_thisobject = obj; \
+	int enumval = ast_geoloc_ ## _stem ## _str_to_enum(var->value); \
+	if (enumval == -1) { \
+		return -1; \
+	} \
+	_thisobject->_stem = enumval; \
+	return 0; \
+}
+
+
+#define GEOLOC_ENUM_TO_NAME(_stem) \
+const char * ast_geoloc_ ## _stem ## _to_name(int ix) \
+{ \
+	if (!ARRAY_IN_BOUNDS(ix, _stem ## _names)) { \
+		return "none"; \
+	} else { \
+		return _stem ## _names[ix]; \
+	} \
+}
+
+#define CONFIG_ENUM_TO_STR(_object, _stem) \
+static int _object ## _ ## _stem ## _to_str(const void *obj, const intptr_t *args, char **buf) \
+{ \
+	const struct ast_geoloc_ ## _object *_thisobject = obj; \
+	if (!ARRAY_IN_BOUNDS(_thisobject->_stem, _stem ## _names)) { \
+		*buf = ast_strdup("none"); \
+	} else { \
+		*buf = ast_strdup(_stem ## _names[_thisobject->_stem]); \
+	} \
+	return 0; \
+}
+
+#define CONFIG_ENUM(_object, _stem) \
+CONFIG_STR_TO_ENUM(_stem) \
+GEOLOC_ENUM_TO_NAME(_stem) \
+CONFIG_ENUM_HANDLER(_object, _stem) \
+CONFIG_ENUM_TO_STR(_object, _stem)
+
+#define CONFIG_VAR_LIST_HANDLER(_object, _stem) \
+static int  _object ## _ ## _stem ## _handler(const struct aco_option *opt, struct ast_variable *var, void *obj) \
+{ \
+	struct ast_geoloc_ ## _object *_thisobject = obj; \
+	struct ast_variable *new_var; \
+	char *item_string, *item, *item_name, *item_value; \
+	int rc = 0;\
+	if (ast_strlen_zero(var->value)) { return 0; } \
+	item_string = ast_strdupa(var->value); \
+	while ((item = ast_strsep(&item_string, ',', AST_STRSEP_ALL))) { \
+		item_name = ast_strsep(&item, '=', AST_STRSEP_ALL); \
+		item_value = ast_strsep(&item, '=', AST_STRSEP_ALL); \
+		new_var = ast_variable_new(item_name, S_OR(item_value, ""), ""); \
+		if (!new_var) { \
+			rc = -1; \
+			break; \
+		} \
+		ast_variable_list_append(&_thisobject->_stem, new_var); \
+	} \
+	return rc; \
+}
+
+#define CONFIG_VAR_LIST_DUP(_object, _stem) \
+static int  _object ## _ ## _stem ## _dup(const void *obj, struct ast_variable **fields) \
+{ \
+	const struct ast_geoloc_ ## _object *_thisobject = obj; \
+	if (_thisobject->_stem) { \
+		*fields = ast_variables_dup(_thisobject->_stem); \
+	} \
+	return 0; \
+}
+
+#define CONFIG_VAR_LIST_TO_STR(_object, _stem) \
+static int  _object ## _ ## _stem ## _to_str(const void *obj, const intptr_t *args, char **buf) \
+{ \
+	const struct ast_geoloc_ ## _object *_thisobject = obj; \
+	struct ast_str *str = ast_variable_list_join(_thisobject->_stem, ",", "=", "\"", NULL); \
+	*buf = ast_strdup(ast_str_buffer(str)); \
+	ast_free(str); \
+	return 0; \
+}
+
+#define CONFIG_VAR_LIST(_object, _stem) \
+CONFIG_VAR_LIST_HANDLER(_object, _stem) \
+CONFIG_VAR_LIST_DUP(_object, _stem) \
+CONFIG_VAR_LIST_TO_STR(_object, _stem)
+
+int geoloc_config_load(void);
+int geoloc_config_reload(void);
+int geoloc_config_unload(void);
+
+struct ast_xml_node *geoloc_civicaddr_list_to_xml(const struct ast_variable *resolved_location,
+	const char *ref_string);
+int geoloc_civicaddr_load(void);
+int geoloc_civicaddr_unload(void);
+int geoloc_civicaddr_reload(void);
+
+struct ast_xml_node *geoloc_gml_list_to_xml(const struct ast_variable *resolved_location,
+	const char *ref_string);
+int geoloc_gml_unload(void);
+int geoloc_gml_load(void);
+int geoloc_gml_reload(void);
+
+int geoloc_dialplan_unload(void);
+int geoloc_dialplan_load(void);
+int geoloc_dialplan_reload(void);
+
+int geoloc_channel_unload(void);
+int geoloc_channel_load(void);
+int geoloc_channel_reload(void);
+
+int geoloc_eprofile_unload(void);
+int geoloc_eprofile_load(void);
+int geoloc_eprofile_reload(void);
+
+struct ast_sorcery *geoloc_get_sorcery(void);
+
+struct ast_variable *geoloc_eprofile_resolve_varlist(struct ast_variable *source,
+	struct ast_variable *variables, struct ast_channel *chan);
+
+
+#endif /* GEOLOC_PRIVATE_H_ */
diff --git a/res/res_geolocation/pidf_lo_test.xml b/res/res_geolocation/pidf_lo_test.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bea98d6139c778a2c466703c0c4e14c0ecd5edea
--- /dev/null
+++ b/res/res_geolocation/pidf_lo_test.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<presence entity="pres:alice@asterisk.org"
+	xmlns="urn:ietf:params:xml:ns:pidf"
+	xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
+	xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
+	xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
+	xmlns:gml="http://www.opengis.net/gml"
+	xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
+	xmlns:con="urn:ietf:params:xml:ns:geopriv:conf"
+	xmlns:gs="http://www.opengis.net/pidflo/1.0">
+	<tuple id="point-2d">
+		<status>
+			<gp:geopriv>
+				<gp:location-info>
+					<gml:Point srsName="urn:ogc:def:crs:EPSG::4326">
+						<gml:pos>-34.410649 150.87651</gml:pos>
+					</gml:Point>
+				<con:confidence pdf="normal">66</con:confidence>
+				</gp:location-info>
+				<gp:usage-rules>
+					<gbp:retransmission-allowed>no</gbp:retransmission-allowed>
+					<gbp:retention-expiry>2010-11-14T20:00:00Z</gbp:retention-expiry>
+				</gp:usage-rules>
+				<gp:method>Manual</gp:method>
+				<gp:note-well>
+					this is a test
+					of the emergency broadcast system
+				</gp:note-well>
+			</gp:geopriv>
+		</status>
+		<timestamp>2007-06-22T20:57:29Z</timestamp>
+	</tuple>
+</presence>
diff --git a/res/res_geolocation/pidf_to_eprofile.xslt b/res/res_geolocation/pidf_to_eprofile.xslt
new file mode 100644
index 0000000000000000000000000000000000000000..95f000c8252621200d25b4c0924882f6b227a52d
--- /dev/null
+++ b/res/res_geolocation/pidf_to_eprofile.xslt
@@ -0,0 +1,213 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0"
+	xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
+	xmlns:def="urn:ietf:params:xml:ns:pidf"
+	xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
+	xmlns:fn="http://www.w3.org/2005/xpath-functions"
+	xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
+	xmlns:gml="http://www.opengis.net/gml"
+	xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
+	xmlns:gs="http://www.opengis.net/pidflo/1.0"
+	xmlns:con="urn:ietf:params:xml:ns:geopriv:conf"
+	xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+
+<!--
+	The whole purpose of this stylesheet is to convert a PIDF-LO document into a simple,
+	common XML document that is easily parsable by geoloc_eprofile into an eprofile.
+
+	For example:
+
+	<presence>
+		<device>
+			<location-info format="GML">shape="Point", crs="2d", pos="38.456 -105.678"</location-info>
+			<usage-rules>retransmission-allowed=no</usage-rules>
+			<method>GPS</method>
+		</device>
+	</presence>
+
+	WARNING:  Don't mess with this stylesheet before brushing up your
+	XPath and XSLT expertise.
+-->
+
+
+<!--
+	All of the namespaces that could be in the incoming PIDF-LO document
+	have to be declared above.  All matching is done based on the URI, not
+	the prefix so we can use whatever prefixes we want.  For instance,
+	even if "urn:ietf:params:xml:ns:pidf:data-model" were declared with
+	the "pdm" prefix in the incoming document and with "dm" here,
+	"dm:device" would match "pdm:device" in the document.
+-->
+
+	<xsl:output method="xml" indent="yes"/>
+	<xsl:strip-space elements="*"/>
+
+	<!--
+		Even though the "presence", "tuple", and "status" elements won't have namespaces in the
+		incoming PIDF document, we have to use the pseudo-namespace "def" here because of namespace
+		processing quirks in libxml2 and libxslt.  We don't use namespace prefixes in the output
+		document at all.
+	-->
+	<xsl:template match="/def:presence">
+		<xsl:element name="presence">
+			<xsl:attribute name="entity"><xsl:value-of select="@entity"/></xsl:attribute>
+			<!--
+				We only want devices, tuples and persons (in that order) that
+				have location-info elements.
+			 -->
+			<xsl:apply-templates select="dm:device[./gp:geopriv/gp:location-info]"/>
+			<xsl:apply-templates select="def:tuple[./def:status/gp:geopriv/gp:location-info]"/>
+			<xsl:apply-templates select="dm:person[.//gp:geopriv/gp:location-info]"/>
+		</xsl:element>
+	</xsl:template>
+
+	<xsl:template name="geopriv">
+			<xsl:apply-templates select=".//gp:geopriv/gp:location-info"/>
+			<xsl:apply-templates select=".//gp:geopriv/gp:usage-rules"/>
+			<xsl:apply-templates select=".//gp:geopriv/gp:method"/>
+			<xsl:apply-templates select=".//gp:geopriv/gp:note-well"/>
+	</xsl:template>
+
+	<xsl:template match="def:tuple">
+		<xsl:element name="tuple">
+			<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
+			<xsl:call-template name="geopriv"/>
+			<xsl:apply-templates select="./def:timestamp"/>
+		</xsl:element>
+	</xsl:template>
+
+	<xsl:template match="dm:device|dm:person">
+		<xsl:element name="{local-name(.)}">
+			<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
+			<xsl:call-template name="geopriv"/>
+			<xsl:apply-templates select="./dm:timestamp"/>
+			<!-- deviceID should only apply to devices -->
+			<xsl:if test="./dm:deviceID">
+				<deviceID>
+					<xsl:value-of select="./dm:deviceID"/>
+				</deviceID>
+			</xsl:if>
+		</xsl:element>
+	</xsl:template>
+
+	<xsl:template match="gp:geopriv/gp:location-info">
+		<xsl:element name="location-info">
+			<xsl:choose>
+				<xsl:when test="ca:civicAddress">
+					<xsl:attribute name="format">civicAddress</xsl:attribute>
+				</xsl:when>
+				<xsl:when test="gml:*">
+					<xsl:attribute name="format">gml</xsl:attribute>
+				</xsl:when>
+				<xsl:when test="gs:*">
+					<xsl:attribute name="format">gml</xsl:attribute>
+				</xsl:when>
+			</xsl:choose>
+			<xsl:apply-templates/>  <!-- Down we go! -->
+		</xsl:element>
+	</xsl:template>
+
+	<!-- Civic Address -->
+	<xsl:template match="gp:location-info/ca:civicAddress">
+		<xsl:element name="civicAddress">
+			<xsl:attribute name="lang"><xsl:value-of select="@xml:lang"/></xsl:attribute>
+			<!-- The for-each seems to be slightly faster than applying another template -->
+			<xsl:for-each select="./*">
+				<xsl:call-template name="name-value" />
+			</xsl:for-each>
+		</xsl:element>
+	</xsl:template>
+
+	<!-- End of Civic Address.  Back up to location-info. -->
+
+	<!-- The GML shapes:  gml:Point, gs:Circle, etc. -->
+	<xsl:template match="gp:location-info/gml:*|gp:location-info/gs:*">
+		<xsl:element name="{local-name(.)}">
+			<xsl:choose>
+			<xsl:when test="@srsName = 'urn:ogc:def:crs:EPSG::4326'">
+				<xsl:attribute name="srsName">2d</xsl:attribute>
+			</xsl:when>
+			<xsl:when test="@srsName = 'urn:ogc:def:crs:EPSG::4979'">
+				<xsl:attribute name="srsName">3d</xsl:attribute>
+			</xsl:when>
+			<xsl:otherwise>
+				<xsl:attribute name="srsName">unknown</xsl:attribute>
+			</xsl:otherwise>
+			</xsl:choose>
+			<xsl:apply-templates />  <!-- Down we go! -->
+		</xsl:element>
+	</xsl:template>
+
+	<!-- The supported GML attributes -->
+	<xsl:template match="gs:orientation"><xsl:call-template name="angle" /></xsl:template>
+	<xsl:template match="gs:radius"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:height"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:semiMajorAxis"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:semiMinorAxis"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:verticalAxis"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:innerRadius"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:outerRadius"><xsl:call-template name="length" /></xsl:template>
+	<xsl:template match="gs:startAngle"><xsl:call-template name="angle" /></xsl:template>
+	<xsl:template match="gs:openingAngle"><xsl:call-template name="angle" /></xsl:template>
+	<xsl:template match="gml:pos"><xsl:call-template name="name-value" /></xsl:template>
+	<xsl:template match="gml:posList"><xsl:call-template name="name-value" /></xsl:template>
+
+	<!-- The GML attribute types -->
+	<xsl:template name="name-value">
+		<xsl:element name="{local-name(.)}">
+			<xsl:value-of select="normalize-space(.)"/>
+		</xsl:element>
+	</xsl:template>
+
+	<xsl:template name="length"><xsl:call-template name="name-value" /></xsl:template>
+
+	<xsl:template name="angle">
+		<xsl:element name="{local-name(.)}">
+			<xsl:choose>
+				<xsl:when test="@uom = 'urn:ogc:def:uom:EPSG::9102'">
+					<xsl:attribute name="uom">radians</xsl:attribute></xsl:when>
+				<xsl:otherwise>
+					<xsl:attribute name="uom">degrees</xsl:attribute></xsl:otherwise>
+			</xsl:choose>
+			<xsl:value-of select="normalize-space(.)"/>
+		</xsl:element>
+	</xsl:template>
+
+	<!-- End of GML.  Back up to location-info -->
+
+	<xsl:template match="gp:location-info/con:confidence">
+		<xsl:element name="{local-name(.)}">
+			<xsl:attribute name="pdf"><xsl:value-of select="@pdf"/></xsl:attribute>
+			<xsl:value-of select="normalize-space(.)" />
+		</xsl:element>
+	</xsl:template>
+
+	<!-- End of location-info.  Back up to geopriv -->
+
+	<xsl:template match="gp:geopriv/gp:usage-rules">
+		<xsl:element name="usage-rules">
+			<xsl:for-each select="./*">
+				<xsl:call-template name="name-value" />
+			</xsl:for-each>
+		</xsl:element>
+	</xsl:template>
+
+	<xsl:template match="gp:geopriv/gp:method">
+		<xsl:call-template name="name-value" />
+	</xsl:template>
+
+	<xsl:template match="gp:geopriv/gp:note-well">
+		<xsl:element name="note-well">
+			<xsl:value-of select="." />
+		</xsl:element>
+	</xsl:template>
+
+	<!-- End of geopriv.  Back up to device/tuple/person -->
+
+	<xsl:template match="def:timestamp|dm:timestamp">
+		<xsl:call-template name="name-value" />
+	</xsl:template>
+
+
+</xsl:stylesheet>
diff --git a/res/res_geolocation/wiki/AsteriskImplementation.md b/res/res_geolocation/wiki/AsteriskImplementation.md
new file mode 100644
index 0000000000000000000000000000000000000000..095e051547cf43221da4248920c3683ebad38aa4
--- /dev/null
+++ b/res/res_geolocation/wiki/AsteriskImplementation.md
@@ -0,0 +1,312 @@
+{section:border=false}
+{column:width=70%}
+
+h1. Introduction
+
+The Geolocation capabilities are implemented in Asterisk with the res_geolocation and res_pjsip_geolocation modules and the geolocation.conf configuration file.  There are also dialplan functions which allow you to manipulate location information as it's passed through the dialplan.
+
+h1. Location Information Flow
+
+Location information can be supplied to Asterisk from several sources during the call flow...
+* Sent by a caller in a SIP INVITE message.
+* Provided by a geolocation profile attached to the caller's endpoint.
+* Provided by the dialplan via the Geolocation apps and functions.
+* Provided by a geolocation profile attached to the callee's endpoint.
+
+These sources aren't mutually exclusive and may, in fact, provide conflicting information or present the same information in multiple formats.  Given that, there's no way for Asterisk to merge information nor is there a way for Asterisk to automatically determine which source should take precedence.  However, you can use the geolocation profiles and the dialplan functions to tell Asterisk what to do with the location information received from the previous step in the call flow.
+
+h1. Core Configuration
+The bulk of the geolocation support is implemented in the res_geolocation module and configured in the geolocation.conf file.  The file contains two main objects, Location and Profile.
+
+h2. Common Behavior
+
+h3. Sub-parameters
+Some of the parameters in each object are actually lists of comma-separated name-value "sub-parameters".   For example, the {{location_info}} parameter in the Location object contains a list of sub-parameters that are specific to the location type.  For instance, a GML Circle might look like this:
+{code}
+location_info = shape=Circle, pos="39.12345 -105.98766", radius=100
+{code}
+Spaces around the equals signs and commas are ignored so you must double quote sub-parameter values with spaces or commas in them.
+
+For readability, parameters that use sub-parameters can be split over more than one line.  For example:
+{code}
+location_info = country=US,A1="New York"
+location_info = HNO=1633,PRD=W,RD=46th
+{code}
+would be equivalent to:
+{code}
+location_info = country=US,A1="New York",HNO=1633,PRD=W,RD=46th
+{code}
+
+h3. Variable substitution
+Some of the parameters can contain references to channel variables and dialplan functions.  For example, you might have a URI location object that contains a reference to the {{EXTEN}} channel variable:
+{code}
+location_info = URI=http://some.example.com?key=${EXTEN}
+{code}
+When a call is processed that uses this location object, {{$\{EXTEN\}}} would be replaced with the channel's extension and would result in a URI such as {{http://some.example.com?key=1000}}.  You'd set up your web server to return a location document based on the value of "key".
+
+You can also use dialplan functions such as {{CURL}} and {{ODBC_SQL}} to supply values just as you would in extensions.conf.
+
+h2. Configuration Objects
+h3. Location
+The Location object defines a discrete location or defines a template that can be used to define a discrete location on a per-call basis.
+
+h4. Parameters
+
+* *type*: Object type. Must be "location"
+** Required: yes
+** Uses channel variables: no
+** Sub-parameters: none
+** Default: none
+** Example:
+	{{type=location}}
+
+* *format*: Must be one of "civicAddress", "GML" or "URI" to indicate how the location is expressed.
+** Required: yes
+** Uses channel variables: no
+** Sub-parameters: none
+** Default: none
+** Example:
+	{{format=civicAddress}}
+
+* *method*: If provided, it MUST be one of "GPS", "A-GPS", "Manual", "DHCP", "Triangulation", "Cell", "802.11"
+** Required: no
+** Uses channel variables: no
+** Sub-parameters: none
+** Default: none
+** Example:
+	{{method=Manual}}
+
+* *location_source*: If provided, it MUST be a fully qualified domain name.  IP addresses are specifically not allowed. See [RFC8787|Geolocation Reference Information#rfc8787] for the exact definition of this parameter.
+** Required: no
+** Uses channel variables: no
+** Sub-parameters: none
+** Default: none
+** Example:
+	{{location_source=some.domain.net}}
+
+* *location_info*: Sub-parameters that describe the location.  Dependent on the format selected.
+** Required: yes
+** Uses channel variables: yes
+** Sub-parameters: yes
+** Default: none
+** Examples:
+*** [URI] format: (see the [URI] page for more info)
+	{{location_info = URI=http://some.example.com}}
+*** [civicAddress|Civic Address] format: ( See the [civicAddress|Civic Address] page for more info)
+	{{location_info = country=US, A1="New York", A3="New York", ...}}
+*** [GML|Geography Markup Language] format: (See the [GML|Geography Markup Language] page for more info)
+	{{location_info = shape=Circle, pos="39.12345 -105.98766", radius=100}}
+
+* *confidence*: This is a rarely used field in the specification that would indicate the confidence in the location specified.  See [RFC7459|https://www.rfc-editor.org/rfc/rfc7459] for exact details.
+** Required: no
+** Uses channel variables: no
+** Sub-parameters: yes
+*** *pdf*: One of: "unknown", "normal", "rectangular".
+*** *value*: 0-100 percent indicating the confidence level.
+** Default: none
+** Example:
+	{{confidence = pdf=normal, confidence=95
+
+h4. Example
+
+{code}
+[mylocation]
+type = location
+format = civicAddress
+method = Manual
+location_info = country=US, A1="New York", A3="New York",
+location_info = HNO=1633, PRD=W, RD=46th, STS=Street, PC=10222
+{code}
+
+
+h3. Profile
+The Profile object defines how a location is used and is referenced by channel drivers.
+
+h4. Parameters
+
+* *type*: Object type. Must be "profile"
+** Required: yes
+** Uses channel variables: no
+** Sub-parameters: none
+** Default: none
+** Example:
+	{{type=profile}}
+
+* *location_reference*: Specifies the id of a Location object to use.
+** Required: no
+** Uses channel variables: no
+** Sub-parameters: none
+** Default: none
+** Example:
+	{{location_reference=mylocation}}
+
+* *pidf_element*: For Civic Address and GML location formats, this parameter specifies the PIDF element that will carry the location description on outgoing SIP requests.  Must be one of "tuple", "device" or "person".
+** Required: no
+** Uses channel variables: no
+** Sub-parameters: none
+** Default: device
+** Example:
+	{{pidf_element = tuple}}
+
+* *allow_routing_use*: This value controls the value of the {{Geolocation-Routing}} header sent on SIP requests,  Must be "yes" or "no".  See [RFC6442|Geolocation Reference Information#rfc6442] for more information.
+** Required: no
+** Uses channel variables: no
+** Sub-parameters: none
+** Default: no
+** Example:
+	{{allow_routing_use = yes}}
+
+* *profile_precedence*: Specifies which of the available profiles (configured or incoming) takes precedence. NOTE: On an incoming call leg/channel, the "incoming" profile is the one received by the channel driver from the calling party in the SIP INVITE and the "configured" profile is the one attached to the calling party's pjsip endpoint.  On an outgoing call segment/channel, the "incoming" profile is the one received by the channel driver from the Asterisk core/dialplan and the "configured" profile one is the one attached to the called party's pjsip endpoint.
+** Valid values:
+*** {{prefer_incoming}}: Use the incoming profile if it exists and has location information, otherwise use the	configured profile if it has location information. If neither profile has location information, nothing is passed on.
+*** {{prefer_config}}: Use the configured profile if it exists and has location information, otherwise use the incoming profile if it has location information. If neither profile has location information, nothing is passed on.
+*** {{discard_incoming}}: Discard the incoming profile and use the configured profile if it has location information. If it doesn't, nothing is passed on.
+*** {{discard_config}}: Discard the configured profile and use the incoming profile if it has location information. If it doesn't, nothing is passed on.
+** Required: no
+** Uses channel variables: no
+** Sub-parameters: none
+** Default: discard_incoming
+** Example:
+	{{profile_precedence = prefer_incoming}}
+
+* *usage_rules*: For Civic Address and GML location formats, this parameter specifies the contents of the {{usage-rules}} PIDF-LO element. See [RFC4119|Geolocation Reference Information#rfc4119] for the exact definition of this parameter.
+** Required: no
+** Uses channel variables: yes
+** Sub-parameters: yes
+*** *retransmission-allowed*: Must be "yes" or "no".
+*** *retention-expires*: An ISO-format timestamp after which the recipient MUST discard and location information associated with this request.  The default is 24 hours after the request was sent.  You can use dialplan functions to create a timestamp yourself if needed.
+** Default: retransmission-allowed=no, retention-expires=<current time + 24 hours>
+** Example:
+	{{usage_rules = retransmission-allowed=yes,retention-expires="$\{STRFTIME($[$\{EPOCH\}+3600],UTC,%FT%TZ)\}"}}
+
+* *suppress_empty_ca_elements*: For Civic Address outgoing PIDF-LO documents, don't output empty elements.  This can be useful when you dynamically set values of elements in the dialplan that could evaluate to an empty string.  For instance, if you set the street suffix STS element from a dialplan variable and it happens to be empty, the default behavior would be to send an empty {{<STS/>}} element.  If this parameter is set to "yes" however, we'd just not print the element at all.
+** Required: no
+** Uses channel variables: no
+** Sub-parameters: no
+** Default: no
+** Example
+	{{suppress_empty_ca_elements = yes}}
+
+* *location_info_refinement*: This parameter can be used to refine referenced location by adding these sub-parameters to the {{location_info}} parameter of the referenced location object.  For example, you could have Civic Address referenced object describe a building, then have this profile refine it by adding floor, room, etc.  Another profile could then also reference the same location object and refine it by adding a different floor, room, etc.
+** Required: no
+** Uses channel variables: yes
+** Sub-parameters: yes (any that can appear in a location's location_info parameter)
+** Default: none
+** Example:
+	Add a room to the civicAddress specified by location_reference.
+	{{location_reference = myCivicAddress = ROOM=23A4}}
+	{{location_info_refinement = ROOM=23A4}}
+
+* *location_variables*: Any parameter than can use channel variables can also use the arbitrary variables defined in this parameter.  For example {{location_variables = MYVAR1=something, MYVAR2="something else"}} would allow you to use {{$\{MYVAR1\}}} and {{$\{MYVAR2\}}} in any other parameter that can accept channel variables.
+** Required: no
+** Uses channel variables: yes
+** Sub-parameters: yes (one or more name=value pairs)
+** Default: none
+** Example:
+	{{location_variables = MYVAR1=something, MYVAR2="something else"}}
+
+* *notes*: The specifications allow a free-form "note-well" element to be added to the location description.  Any text entered here will be present on all outgoing Civic Address and GML requests.
+** Required: no
+** Uses channel variables: no
+** Sub-parameters: no
+** Default: none
+** Example:
+	{{notes = "anything you want"}}
+
+h4. Additional Parameters
+In addition to the profile-specific parameters defined above, any location-object parameters can be specified as well.  This is a convenient shortcut if you have a 1<>1 relationship between profile and location.
+
+h4. Built-in Profiles
+In addition to the profiles you define in geolocation.conf, 4 built-in profiles are also available They're named after their profile_precedence setting:
+* *<prefer_incoming>*
+* *<prefer_config>*
+* *<discard_incoming>*
+* *<discard_config>*
+
+The rest of the profile parameters are set to their defaults.
+
+h1. chan_pjsip Configuration
+Two new parameters have been added to pjsip endpoints:
+
+h2. Parameters
+
+* *geoloc_incoming_call_profile*: Should be set to the name of a geolocation profile to use for calls coming into Asterisk from this remote endpoint.  If not set, no geolocation processing will occur and any location descriptions present on the incoming request will be silently dropped.  Any of the 4 built-in profiles can be used.
+
+* *geoloc_outgoing_call_profile*: Should be set to the name of a geolocation profile to use for calls Asterisk sends to this remote endpoint.  If not set, no geolocation processing will occur and any location descriptions coming from the associated incoming channel or the dialplan will be silently dropped and not conveyed to the endpoint. Any of the 4 built-in profiles can be used.
+
+Example:
+{code}
+[myendpoint]
+type = endpoint
+...
+geoloc_incoming_call_profile = <discard_incoming>
+geoloc_outgoing_call_profile = myendpoint_profile
+{code}
+
+h1. Dialplan Function
+A new dialplan function has been added to allow a dialplan author to manipulate geolocation information.
+
+h2. GEOLOC_PROFILE
+This function can get or set any of the fields in a specific profile.  The available fields are those in _both_ the Location and Profile configuration objects.  See the fuinction help for more information.
+
+h1. Example Call Flows
+
+h2. Simple Example 1
+Alice and Bob work in the same building so in geolocation.conf, we can define a location that describes the building and profiles for Bob and Alice that add floor and room.  We're assuming here that Bob's and Alice's phones don't send any location information themselves.
+{code}
+[building1]
+type = location
+format = civicAddress
+location_info = country=US, A1="New York", A3="New York",
+location_info = HNO=1633, PRD=W, RD=46th, STS=Street, PC=10222
+method = Manual
+
+[alice]
+type = profile
+location_reference = building1
+location_refinement = FLR=4, ROOM=4B20
+
+[bob]
+type = profile
+location_reference = building1
+location_refinement = FLR=32, ROOM=32A6
+{code}
+
+In pjsip.conf, we can now associate those profiles to endpoints.
+{code}
+[bob]
+type = endpoint
+geoloc_incoming_call_profile = bob
+
+[alice]
+type = endpoint
+geoloc_incoming_call_profile = alice
+{code}
+You'll notice that neither bob nor alice set {{geoloc_outgoing_call_profile}} because we never want to send location information _to_ them.
+
+Now when Alice makes a call, Asterisk will construct an effective profile (including any defaults and variable substitutions) that looks like this...
+{code}
+format = civicAddress
+location_info = country=US, A1="New York", A3="New York",
+location_info = HNO=1633, RD=46th, STS=Street, PC=10222, FLR=4, ROOM=4B20
+method = Manual
+usage_rules = retransmission-allowed=no
+usage_rules = retention-expires="${STRFTIME($[${EPOCH}+86400],UTC,%FT%TZ)}"
+allow_routing = no
+pidf_element = device
+{code}
+
+Bob's effective profile would be exactly the same except for {{FLR}} and {{ROOM}}
+
+This effective profile will then be forwarded to the dialplan.  The dialplan application can then use GEOLOC_PROFILE to make changes before the effective profile is forwarded to the outgoing channel.  It can also use GeolocProfileDelete to just delete the effective profile and pass nothing.
+
+{column}
+{column:width=30%}
+Table of Contents:
+{toc}
+
+
+Geolocation:
+{pagetree:root=Geolocation|expandCollapseAll=true}
+{column}
+{section}
diff --git a/res/res_geolocation/wiki/CivicAddress.md b/res/res_geolocation/wiki/CivicAddress.md
new file mode 100644
index 0000000000000000000000000000000000000000..f18c02765ca7cca46279f806776c6c04d14c4ca8
--- /dev/null
+++ b/res/res_geolocation/wiki/CivicAddress.md
@@ -0,0 +1,167 @@
+{section:border=false}
+{column:width=70%}
+
+h1. Introduction
+For static locations, using Civic Address location descriptions would be the easiest method.  As stated earlier though, you and your partners must agree on which description formats are acceptable.
+
+The following tables list the IANA registered element names that are currently accepted. The complete list of codes is defined in:
+[https://www.iana.org/assignments/civic-address-types-registry/civic-address-types-registry.xhtml]
+
+These codes were originally defined in [RFC4119|Geolocation Reference Information#rfc4119] and [RFC4776|Geolocation Reference Information#rfc4776]
+|| Label || Description || Example |
+| country | The country is identified by the two-letter ISO 3166 code.|US|
+| A1 | national subdivisions (state, region, province, prefecture)|New York|
+| A2 | county, parish, gun (JP), district (IN)|King's County|
+| A3 | city, township, shi (JP)|New York|
+| A4 | city division, borough, city, district, ward, chou (JP)|Manhattan|
+| A5 | neighborhood, block | Morningside Heights |
+| A6 | street\\NOTE: This code has been deprecated in favor of {{RD}}, defined below. | Broadway |
+| PRD | Leading street direction| N, W |
+| POD | Trailing street direction| SW |
+| STS | Street suffix | Avenue, Platz, Street|
+| HNO | House number, numeric part only|123|
+| HNS | House number suffix | A, 1/2 |
+| LMK | Landmark or vanity address|Low Library |
+| LOC | Additional location information\\NOTE: {{ROOM}} was added below.| Room 543 |
+| FLR | Floor | 5 |
+| NAM | Name (residence, business or office occupant)|Joe's Barbershop |
+| PC | Postal code | 10027-0401 |
+
+These codes were added in [RFC5139|Geolocation Reference Information#rfc5139]
+
+|| Label || Description || Example |
+| BLD | Building (structure) | Hope Theatre |
+| UNIT | Unit (apartment, suite) | 12a |
+| ROOM | Room | 450F |
+| PLC | Place-type | office |
+| PCN | Postal community name | Leonia |
+| POBOX | Post office box (P.O. box) | U40 |
+| ADDCODE | Additional Code | 13203000003 |
+| SEAT | Seat (desk, cubicle, workstation) | WS 181 |
+| RD | Primary road or street | Broadway |
+| RDSEC | Road section | 14 |
+| RDBR | Road branch | Lane 7 |
+| RDSUBBR | Road sub-branch | Alley 8 |
+| PRM | Road pre-modifier | Old |
+| POM | Road post-modifier | Service |
+
+These codes were added in [RFC6848|Geolocation Reference Information#rfc6848]
+
+|| Label || Description || Example |
+|PN|Post number that is attributed to a lamp post or utility pole.|21344567|
+|MP|Milepost: a marker indicating distance to or from a place (often a town)
+May actually be expressed in "miles" or "kilometers".|237.4|
+|STP|Street Type Prefix.|Boulevard|
+|HNP|House Number Prefix.|Z|
+
+h1. Example Configurations
+
+h2. Simple Example 1
+In geolocation.conf, we can define a location that describes a building and profiles for Bob and Alice that add floor and room.  We're assuming here that Bob's and Alice's phones don't send any location information themselves.
+{code}
+[building1]
+type = location
+format = civicAddress
+location_info = country=US, A1="New York", A3="New York",
+location_info = HNO=1633, PRD=W, RD=46th, STS=Street, PC=10222
+method = Manual
+
+[alice]
+type = profile
+location_reference = building1
+location_refinement = FLR=4, ROOM=4B20
+
+[bob]
+type = profile
+location_reference = building1
+location_refinement = FLR=32, ROOM=32A6
+{code}
+
+h1. PIDF-LO XML Examples
+
+Here's what Alice's PIDF-LO would look like:
+{code}
+<?xml version="1.0" encoding="UTF-8"?>
+<presence entity="pres:alice@example.com"
+	xmlns="urn:ietf:params:xml:ns:pidf"
+	xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
+	xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
+	xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
+	xmlns:gml="http://www.opengis.net/gml"
+	xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
+	xmlns:gs="http://www.opengis.net/pidflo/1.0">
+	<dm:device>
+		<gp:geopriv>
+			<gp:location-info>
+				<ca:civicAddress xml:lang="en-AU">
+					<ca:country>US</ca:country>
+					<ca:A1>New York</ca:A1>
+					<ca:A3>New York</ca:A3>
+					<ca:HNO>1633</ca:HNO>
+					<ca:PRD>W</ca:PRD>
+					<ca:RD>46th</ca:RD>
+					<ca:STS>Street</ca:STS>
+					<ca:PC>10222</ca:PC>
+					<ca:FLR>4</ca:FLR>
+					<ca:ROOM>4B20</ca:ROOM>
+				</ca:civicAddress>
+			</gp:location-info>
+			<gp:usage-rules>
+			</gp:usage-rules>
+			<gp:method>manual</gp:method>
+		</gp:geopriv>
+		<dm:deviceID>mac:1234567890ab</dm:deviceID>
+		<dm:timestamp>2022-04-22T20:57:29Z</dm:timestamp>
+	</dm:device>
+</presence>
+{code}
+
+Here's what Bob's PIDF-LO would look like:
+{code}
+<?xml version="1.0" encoding="UTF-8"?>
+<presence entity="pres:bob@example.com"
+	xmlns="urn:ietf:params:xml:ns:pidf"
+	xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
+	xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
+	xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
+	xmlns:gml="http://www.opengis.net/gml"
+	xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
+	xmlns:gs="http://www.opengis.net/pidflo/1.0">
+	<dm:device>
+		<gp:geopriv>
+			<gp:location-info>
+				<ca:civicAddress xml:lang="en-AU">
+					<ca:country>US</ca:country>
+					<ca:A1>New York</ca:A1>
+					<ca:A3>New York</ca:A3>
+					<ca:HNO>1633</ca:HNO>
+					<ca:PRD>W</ca:PRD>
+					<ca:RD>46th</ca:RD>
+					<ca:STS>Street</ca:STS>
+					<ca:PC>10222</ca:PC>
+					<ca:FLR>32</ca:FLR>
+					<ca:ROOM>32A6</ca:ROOM>
+				</ca:civicAddress>
+			</gp:location-info>
+			<gp:usage-rules>
+			</gp:usage-rules>
+			<gp:method>manual</gp:method>
+		</gp:geopriv>
+		<dm:deviceID>mac:1234567890ab</dm:deviceID>
+		<dm:timestamp>2022-04-22T20:57:29Z</dm:timestamp>
+	</dm:device>
+</presence>
+{code}
+
+Note that the only civicAddress difference between the two are the {{FLR}} and {{ROOM}}.
+
+{column}
+{column:width=30%}
+Table of Contents:
+{toc}
+
+
+Geolocation:
+{pagetree:root=Geolocation|expandCollapseAll=true}
+{column}
+{section}
diff --git a/res/res_geolocation/wiki/GML.md b/res/res_geolocation/wiki/GML.md
new file mode 100644
index 0000000000000000000000000000000000000000..c1b1a71c90c2208d8e4a273a8d63ae5aa7887ab2
--- /dev/null
+++ b/res/res_geolocation/wiki/GML.md
@@ -0,0 +1,82 @@
+{section:border=false}
+{column:width=70%}
+
+h1. Introduction
+All compliant participants are required to support GML as the description language but it's really only suitable for mobile devices.  As stated earlier though, you and your partners must agree on which description formats are acceptable.
+
+The language itself is fairly simple.  There are 8 shapes that can be used to describe a location and they share a common set of attributes described below.  Determining the actual values for those attributes though can be quite complex and is not covered here.
+
+h2. References:
+* [Open Geospatial Consortium Geography Markup Language|gml]
+* [GML 3.1.1 PIDF-LO Shape Application Schema|geoshape]
+* [Universal Geographical Area Description (GAD)|gad] (for background)
+
+h2. Coordinate Reference Systems
+The coordinate reference system (crs) for a shape specifies whether the points that define a shape express a two dimensional or three dimensional point in space.  It does NOT specify whether the shape itself is 2D or 3D.  For instance, a Point is a one dimensional "shape" but it can be specified with just a latitude and longitude (2d) or latitude, longitude and altitude (3d).  The `crs` is specified for each shape with the `crs` attribute whose value can be either `2d` or `3d`.
+
+h2. Units of Measure
+h3. Position
+Positions are always specified in decimal degrees latitude and longitude.  A 3d position adds the altitude in meters.  `pos` and `pos3d` are the two attributes that specify position.
+h3. Distance
+Distance is _always_ specified in  meters.  `height`, `radius` and the altitude component of `pos` are some of the distance attributes.
+
+*A special note about altitude:* As of the date of this writing (May 2022) we couldn't find any mention in the RFCs concerning the altitude reference.  Is it above:
+# Ground Level (AGL)
+# Mean Sea Level (MSL)
+# A Geoid reference (which one?)
+
+h3. Angle
+Angle may be specified in either degrees or radians by specifying the `degrees` or `radians` suffix to the angle value.  The default it `degrees` if no suffix is provided.  `orientation`, `startAngle` and `openingAngle` are some of the angle attributes.
+
+h2. Shapes
+See the references above for the exact shape definitions.
+
+|| Shape || Attributes ||
+| Point | pos or pos3d |
+| Circle | pos or pos3d, radius |
+| Sphere | pos3d, radius |
+| Ellipse| pos or pos3d, semiMajorAxis, semiMinorAxis, orientation |
+| ArcBand | pos or pos3d, innerRadius, outerRadius, startAngle, openingAngle |
+| Ellipsoid | pos3d, semiMajorAxis, semiMinorAxis, verticalAxis, orientation |
+| Polygon | 3 or more pos or pos3d |
+| Prism | 3 or more pos3d, height |
+
+
+|| Attribute || Description || Units || Example ||
+| pos | A two dimensional point | Decimal degrees | pos="39.12345 -105.98766" |
+| pos3d | A three dimensional point | Decimal degrees + altitude in meters | pos="39.12345 -105.98766 1690" |
+| radius | Distance | Meters | radius="20" |
+| height | Distance | Meters | height="45" |
+| orientation | Angle | Degrees (default) or Radians | orientation="90", orientation="25 radians" |
+| semiMajorAxis | Distance | Meters | semiMajorAxis="145" |
+| semiMinorAxis | Distance | Meters | semiMinorAxis="145" |
+| innerRadius | Distance | Meters | innerRadius="350" |
+| outerRadius | Distance | Meters | outerRadius="350" |
+| verticalAxis | Distance | Meters | verticalAxis="20" |
+
+h2. Examples:
+
+{code}
+location_info = shape=Point, crs=2d, pos="39.12345 -105.98766"
+location_info = shape=Point, crs=3d, pos="39.12345 -105.98766 1892.0"
+location_info = shape=Circle, crs=2d, pos="39.12345 -105.98766" radius="45"
+location_info = shape=Sphere, crs=3d, pos="39.12345 -105.98766 1902" radius="20"
+location_info = shape=Ellipse, crs=2d, pos="39.12345 -105.98766" semiMajorAxis="20", semiMinorAxis="10", orientation="25 radians"
+location_info = shape=ArcBand, crs=2d, pos="39.12345 -105.98766" innerRadius="1200", outerRadius="1500", startAngle="90", openingAngle="120"
+location_info = shape=Polygon, crs=2d, pos="39.12345 -105.98766", pos=40.7890 -105.98766", pos="40.7890 -106.3456", pos=39.12345 -106.3456"
+location_info = shape=Prism, crs=3d, pos="39.12345 -105.98766 1890", pos="40.7890 -105.98766 1890", pos="40.7890 -106.3456 1890", pos=39.12345 -106.3456 1890", height="45"
+{code}
+
+
+{column}
+{column:width=30%}
+Table of Contents:
+{toc}
+
+
+Geolocation:
+{pagetree:root=Geolocation|expandCollapseAll=true}
+{column}
+{section}
+
+
diff --git a/res/res_geolocation/wiki/Geolocation.md b/res/res_geolocation/wiki/Geolocation.md
new file mode 100644
index 0000000000000000000000000000000000000000..f43343fd73211dc239733aeb9fa1621df294c963
--- /dev/null
+++ b/res/res_geolocation/wiki/Geolocation.md
@@ -0,0 +1,74 @@
+{section:border=false}
+{column:width=70%}
+
+{warning:title=Please Read!}
+Before you go off on a geolocation configuration spree, you'll need to understand a few things about Geolocation itself.
+* It's not a single specification.
+While a good part of the implementation is covered in RFCs, some of it is documented in the Geography Markup Language Specification, the 3GPP Technical Specifications, national organizations like the FCC and National Emergency Number Association in the US, and probably your interfacing carriers.  The last is the most important as you don't want emergency calls dropped or routed to incorrect emergency service centers because of a configuration incompatibility.
+
+* It's been around a while.
+The first references I could find date back to 2002.  Since then there have been innumerable changes including IETF drafts that expired 15 years ago that are still being returned by Google searches.
+
+With that in mind, please do your own research and coordinate closely with your partners to validate your configuration.
+{warning}
+
+h1. Introduction
+
+As it applies to Asterisk, Geolocation is the process of...
+* A channel driver accepting location information in an incoming SIP INVITE, either by reference or by value, then using a geolocation profile to determine the disposition of that information and/or possibly add or delete information.
+* Passing the resulting information (if any) to the dialplan which can also determine the disposition of that information and/or possibly add or delete information.
+* Passing the information from the dialplan to the outgoing channel driver which can also use a geolocation profile to determine the disposition of that information and/or possibly add or delete information.
+* Finally sending the information to another party, either by reference or by value.
+
+
+h1. What's a "location"?
+
+h2. Describing a Location
+There are currently two ways to describe a location.
+
+h3. Geography Markup Language (GML)
+GML allows you to express a location in terms of shapes, coordinates, lengths, angles, etc.  For example, a Point with a latitude, longitude and altitude, or a Sphere with a latitude, longitude, altitude and radius. Other shapes include, Circle, Polygon, Ellipse, Ellipsoid, and Prism.  See [GeoShape|Geolocation Reference Information#geoshape].
+
+GML would most often be used by mobile systems where the originator's location is determined dynamically such as base station, sector antenna, distance, etc.  According to [RFC4119|Geolocation Reference Information#rfc4119] GML is considered to be the "baseline" format and MUST be supported by all implementations.  The _level_ of support is not well defined however.  For instance, a specific implementation may only support a subset of shapes.
+
+h3. Civic Address
+For fixed locations, Civic Address is probably the most used location description method.  It's described with terms like Country, State/Province, City, Neighborhood, Street, House Number, Floor, Room, etc.  Oddly enough, support for Civic Address is NOT required by [RFC4119|Geolocation Reference Information#rfc4119].
+
+Both methods are expressed in XML but which location description method you use is entirely between you and your partners.
+
+h3. Encapsulation
+The IETF chose the "Presence Information Data Format" (PIDF) as the wrapper document for location information which can be placed in {{<tuple>}}, {{<device>}}, or {{<person>}} sub-elements.  BTW, this is the same PIDF used to convey SIP subscription information but Asterisk is only supporting PIDF-LO in INVITE requests at this time.
+
+The specification allows multiple locations in each element, multiple elements in a single PIDF-LO document, _and_ multiple PIDF-LO documents in a single request.  Dealing with multiple locations however is such an extraordinarily complex process that it's not support by Asterisk at this time.  Please read the reference information for the applicable rules.  [RFC5491|Geolocation Reference Information#rfc5491] is a good starting point.
+
+h2. Conveying a Location via SIP
+There are currently two ways to convey a location description regardless of which description method you use.  Both use the {{Geolocation}} SIP message header to indicate where to get the location description document.
+
+h3. By Reference
+This one's simple.  The "reference" is actually URI that the recipient can access that will return an XML document containing the description.  "http" and "https" are the most common URI schemes but there are others.  See [RFC6442|Geolocation Reference Information#rfc6442] above.  An example {{Geolocation}} header might look like: {{Geolocation: <https://geoloc.example.com?location=some_location_reference>}}.
+
+With this method, you are entirely responsible for retrieving location descriptions from URIs you receive and for serving location descriptions for URIs you send.  Asterisk does not attempt to retrieve any information from those URIs.
+
+When sending information to an upstream carrier, it's possible they may give _you_ special URIs to place in Geolocation headers you send them.
+
+h3. By Value
+This method involves sending or receiving a PIDF-LO document attached to a SIP message. For details on how this works generally, See [RFC6442|Geolocation Reference Information#rfc6442] and [RFC5491|Geolocation Reference Information#rfc5491].  An example {{Geolocation}} header might look like: {{Geolocation: <cid:gyytfr@your.pbx.com>}}.  The {{cid}} scheme indicates that the recipient should look in the SIP message body (or bodies since there could also be an SDP for example) for the location document.
+
+h3. Multiple URIs
+Technically, the {{Geolocation}} header can contain multiple URIs and they can be a mix of "by-reference" and "by-value".  The process of dealing with multiple location references is _very_ complex however and should be avoided.
+
+h3. Geolocation-Routing
+[RFC6442|Geolocation Reference Information#rfc6442] also defines the {{Geolocation-Routing}} header which indicates to a recipient that the location information may or may not be used for call routing purposes.  If set to "no" (the default if absent), the recipient MUST NOT use the location information for routing purposes.  If set to "yes", the recipient MAY use the location information for routing purposes and may also reset the value to "no" to prevent downstream systems from using the location information for routing.
+
+Some carriers ignore this header altogether.
+
+{column}
+{column:width=30%}
+Table of Contents:
+{toc}
+
+
+Geolocation:
+{pagetree:root=Geolocation|expandCollapseAll=true}
+{column}
+{section}
diff --git a/res/res_geolocation/wiki/README.txt b/res/res_geolocation/wiki/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..534b82b90bdaebf3a71f2b2763edff2bf2ff82af
--- /dev/null
+++ b/res/res_geolocation/wiki/README.txt
@@ -0,0 +1,31 @@
+README
+
+Don't post this file to the Wiki.  It's developer information for maintaining
+these markup files.
+
+These files are written in Confluence flavored markup so they can be
+uploaded to the public wiki directly.
+See https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html
+for syntax.
+
+Many editors have plugins to render previews in this format so check around
+for your particular editor.  For Eclipse, the Mylyn WikiText plugin does the
+trick.
+
+To upload these files to Confluence, you'll need to edit the corresponding
+live page and paste the contents of the edited file.
+
+* Edit the page corresponding to the file that's been edited.
+* Delete the contents:
+  ** If there's an existing "Wiki Markup" section with markup in it, select
+  the contents of the section and delete it. This should leave you with an
+  empty "Wiki Markup" section.
+  ** If the page is the traditional WYSIWYG Confluence editor, delete the
+  entire contents of the page then type "{markup}" and press return.  Now
+  delete the "{markup}" placeholder that was automatically added to the
+  new "Wiki Markup" section.
+* Copy the entire contents of the edited file into your clipboard.
+* Back in Confluence, paste the contents of your clipboard into the
+  "Wiki Markup" section.
+* Preview if you like then save.
+
diff --git a/res/res_geolocation/wiki/ReferenceInformation.md b/res/res_geolocation/wiki/ReferenceInformation.md
new file mode 100644
index 0000000000000000000000000000000000000000..b949aafc36a1c7f26742d25c14ee6040a3616164
--- /dev/null
+++ b/res/res_geolocation/wiki/ReferenceInformation.md
@@ -0,0 +1,33 @@
+{section:border=false}
+{column:width=70%}
+
+
+There is no single document that has the complete, current specification so please follow and read any "updated by" references in these documents.
+
+|| RFC || Title ||
+|[RFC3693|https://www.rfc-editor.org/rfc/rfc3693]|Geopriv Requirements|
+|[RFC4119|https://www.rfc-editor.org/rfc/rfc4119]|A Presence-based GEOPRIV Location Object Format|
+|[RFC5139|https://www.rfc-editor.org/rfc/rfc5139]|Revised Civic Location Format for\\Presence Information Data Format Location Object (PIDF-LO)|
+|{anchor:rfc5491} [RFC5491|https://www.rfc-editor.org/rfc/rfc5491]|GEOPRIV Presence Information Data Format\\Location Object (PIDF-LO) Usage Clarification, Considerations, and Recommendations|
+|[RFC5808|https://www.rfc-editor.org/rfc/rfc5808]|Requirements for a Location-by-Reference Mechanism|
+|[RFC6280|https://www.rfc-editor.org/rfc/rfc6280]|An Architecture for Location and Location\\Privacy in Internet Applications|
+|{anchor:rfc6442} [RFC6442|https://www.rfc-editor.org/rfc/rfc6442]|Location Conveyance for the Session Initiation Protocol|
+|[RFC6848|https://www.rfc-editor.org/rfc/rfc6848]|Specifying Civic Address Extensions in the\\Presence Information Data Format Location Object (PIDF-LO)|
+|[RFC7459|https://www.rfc-editor.org/rfc/rfc7459]|Representation of Uncertainty and Confidence\\in the Presence Information Data Format Location Object (PIDF-LO)|
+|[RFC8787|https://www.rfc-editor.org/rfc/rfc8787]|Location Source Parameter for the SIP Geolocation Header Field|
+|{anchor:gml} [OGC GML|https://www.ogc.org/standards/gml]|Open Geospatial Consortium Geography Markup Language|
+|{anchor:geoshape} [GeoShape|https://portal.ogc.org/files/?artifact_id=21630#:~:text=This%20GML%203.1.,uses%20the%20separately%20specified%20geoshape]|GML 3.1.1 PIDF-LO Shape Application Schema\\for use by the Internet Engineering Task Force (IETF)|
+|{anchor:gad} [3GPP TS 23.032|https://www.3gpp.org/ftp/Specs/archive/23_series/23.032/]|3GPP Technical Specification: Universal Geographical Area Description (GAD)\\Use version [23.032-h20|https://www.3gpp.org/ftp/Specs/archive/23_series/23.032/23032-h20.zip]\\This document is NOT specific to Geopriv so use with caution|
+
+
+{column}
+{column:width=30%}
+Table of Contents:
+{toc}
+
+
+Geolocation:
+{pagetree:root=Geolocation|expandCollapseAll=true}
+{column}
+{section}
+
diff --git a/res/res_geolocation/wiki/URI.md b/res/res_geolocation/wiki/URI.md
new file mode 100644
index 0000000000000000000000000000000000000000..ad4b83ac9700d558a7c40d1f19d726ba1b226158
--- /dev/null
+++ b/res/res_geolocation/wiki/URI.md
@@ -0,0 +1,85 @@
+{section:border=false}
+{column:width=70%}
+
+h1. Introduction
+
+As mentioned in other pages, Geolocation descriptions can be passed "by-value" using a GML or Civic Address XML document, or "by-reference" using a URI.  This page discusses the latter.
+
+h1. Concepts
+
+h2.  Outgoing Calls
+Passing location descriptions using URIs is fairly simple from an Asterisk perspective.  It does however, require the implementer to establish and maintain infrastructure to handle the serving of those URIs.  Given the critical nature of the information, setting up such infrastructure is not trivial and is beyond the scope of Asterisk and this documentation.
+
+h2. Incoming calls
+On incoming calls, Asterisk will make any "pass-by-reference" URIs available to the dialplan via the {{GEOLOC_PROFILE}} function but will NOT attempt to retrieve any documents from that URI.  It's the dialplan author's responsibility to retrieve, interpret and process such documents.
+
+h1. Example 1
+
+Let's say that every extension in your organization has a public DID associated with it, you have a database that cross references DIDs and office locations, and you have a web server that can be queried with a "GET" request and an "DID" query parameter ({{https://my.company.com/location_query?DID=<did>}}) to get the DID's location.  When someone in your organization dials 911, you want a link sent in the outgoing SIP INVITE that the recipient can call to get the caller's location.
+
+In geolocation.conf, you'd create Location and Profile objects as follows:
+{code}
+[did-xref]
+type = location
+format = URI
+location = URI='https://my.company.com/location_query?DID=${CALLERID(num)}'
+
+[employees-outbound]
+type = profile
+location_reference = did-xref
+{code}
+
+In pjsip.conf, you'd add a {{geoloc_outgoing_call_profile}} parameter to your _outgoing_ endpoint definition:
+{code}
+[my-provider]
+type = endpoint
+...
+geoloc_outgoing_call_profile = employees-outbound
+{code}
+
+Now let's say that Bob has DID {{12125551212}} assigned to him and he makes an outgoing call which is routed to "my-provider".  Asterisk would automatically add the following header to the INVITE:
+{code}
+Geolocation: <https://my.company.com/location_query?DID=12125551212>
+{code}
+The recipient could then make a simply query using that URI and get Bob's location in whatever format was agreed upon with you and them.
+
+Of course, this is a _very_ simple example that would add the Geolocation header to _all_ calls made via "my-provider".  If you only routed emergency calls to "my-provider" this would work fine but you probably don't want to leak location information on non-emergency calls.
+
+h1. Example 2
+
+In this example, we'll use the dialplan apps and functions to decide if we want to send location information to the recipient or not.  In fact, we're not going to use geolocation.conf at all.
+
+In extensions.conf:
+
+{code}
+; The pre dial handler adds a new profile with a URI location to
+; the outgoing channel when 911 is dialed and does nothing if another number is dialed.
+[pre-dial-handler]
+exten = 911,1,NoOp(Entering PDH for Outgoing Channel)
+same  = n,Set(GEOLOC_PROFILE(format)=URI)
+same  = n,Set(GEOLOC_PROFILE(location_info)=URI=https://my.company.com/location_query?DID=${CALLERID(num)})
+same  = n,Return(0)
+exten = _X.,1,Return(0)
+
+[default]
+exten = _X.,1,NoOp(Outgoing call)
+; 'b' will run the pre-dial-handler on the outgoing channel.
+same  = n,Dial(PJSIP/${EXTEN},5,b(pre-dial-handler))
+
+{code}
+
+{column}
+{column:width=30%}
+Table of Contents:
+{toc}
+
+
+Geolocation:
+{pagetree:root=Geolocation|expandCollapseAll=true}
+{column}
+{section}
+
+
+
+
+
diff --git a/res/res_hep.c b/res/res_hep.c
index 3241801b059427a07b145f6fe973dd72ffdcd454..36f7e43ec8261e3a3d8efdcaf75887e8f2470fed 100644
--- a/res/res_hep.c
+++ b/res/res_hep.c
@@ -78,6 +78,9 @@
 				<configOption name="capture_id" default="0">
 					<synopsis>The ID for this capture agent.</synopsis>
 				</configOption>
+				<configOption name="capture_name" default="">
+					<synopsis>The name for this capture agent.</synopsis>
+				</configOption>
 			</configObject>
 		</configFile>
 	</configInfo>
@@ -155,6 +158,9 @@ enum hepv3_chunk_types {
 
 	/*! THE UUID FOR THIS PACKET */
 	CHUNK_TYPE_UUID = 0X0011,
+
+	/*! THE CAPTURE AGENT NAME */
+	CHUNK_TYPE_CAPTURE_AGENT_NAME = 0X0013,
 };
 
 #define INITIALIZE_GENERIC_HEP_IDS(hep_chunk, type) do { \
@@ -240,6 +246,7 @@ struct hepv3_global_config {
 	AST_DECLARE_STRING_FIELDS(
 		AST_STRING_FIELD(capture_address);   /*!< Address to send to */
 		AST_STRING_FIELD(capture_password);  /*!< Password for Homer server */
+		AST_STRING_FIELD(capture_name);      /*!< Capture name for this agent */
 	);
 };
 
@@ -458,7 +465,7 @@ static int hep_queue_cb(void *data)
 	unsigned int packet_len = 0, sock_buffer_len;
 	struct hep_chunk_ip4 ipv4_src, ipv4_dst;
 	struct hep_chunk_ip6 ipv6_src, ipv6_dst;
-	struct hep_chunk auth_key, payload, uuid;
+	struct hep_chunk auth_key, payload, uuid, capturename;
 	void *sock_buffer;
 	int res;
 
@@ -512,6 +519,10 @@ static int hep_queue_cb(void *data)
 		INITIALIZE_GENERIC_HEP_IDS_VAR(&auth_key, CHUNK_TYPE_AUTH_KEY, strlen(config->general->capture_password));
 		packet_len += (sizeof(auth_key) + strlen(config->general->capture_password));
 	}
+	if (!ast_strlen_zero(config->general->capture_name))  {
+		INITIALIZE_GENERIC_HEP_IDS_VAR(&capturename, CHUNK_TYPE_CAPTURE_AGENT_NAME, strlen(config->general->capture_name));
+		packet_len += (sizeof(capturename) + strlen(config->general->capture_name));
+	}
 	INITIALIZE_GENERIC_HEP_IDS_VAR(&uuid, CHUNK_TYPE_UUID, strlen(capture_info->uuid));
 	packet_len += (sizeof(uuid) + strlen(capture_info->uuid));
 	INITIALIZE_GENERIC_HEP_IDS_VAR(&payload,
@@ -556,6 +567,14 @@ static int hep_queue_cb(void *data)
 	memcpy(sock_buffer + sock_buffer_len, capture_info->uuid, strlen(capture_info->uuid));
 	sock_buffer_len += strlen(capture_info->uuid);
 
+	/* Capture Agent Name */
+	if (!ast_strlen_zero(config->general->capture_name)) {
+		memcpy(sock_buffer + sock_buffer_len, &capturename, sizeof(capturename));
+		sock_buffer_len += sizeof(capturename);
+		memcpy(sock_buffer + sock_buffer_len, config->general->capture_name, strlen(config->general->capture_name));
+		sock_buffer_len += strlen(config->general->capture_name);
+	}
+
 	/* Packet! */
 	memcpy(sock_buffer + sock_buffer_len, &payload, sizeof(payload));
 	sock_buffer_len += sizeof(payload);
@@ -681,6 +700,7 @@ static int load_module(void)
 	aco_option_register(&cfg_info, "capture_address", ACO_EXACT, global_options, "", OPT_STRINGFIELD_T, 1, STRFLDSET(struct hepv3_global_config, capture_address));
 	aco_option_register(&cfg_info, "capture_password", ACO_EXACT, global_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct hepv3_global_config, capture_password));
 	aco_option_register(&cfg_info, "capture_id", ACO_EXACT, global_options, "0", OPT_UINT_T, 0, STRFLDSET(struct hepv3_global_config, capture_id));
+	aco_option_register(&cfg_info, "capture_name", ACO_EXACT, global_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct hepv3_global_config, capture_name));
 	aco_option_register_custom(&cfg_info, "uuid_type", ACO_EXACT, global_options, "call-id", uuid_type_handler, 0);
 
 	if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
diff --git a/res/res_http_media_cache.c b/res/res_http_media_cache.c
index a4efadf6e7648b8d4aa302627b9beed58ed9c79e..73386febef6817986399c3e603331948cf96ce88 100644
--- a/res/res_http_media_cache.c
+++ b/res/res_http_media_cache.c
@@ -31,6 +31,41 @@
 	<support_level>core</support_level>
  ***/
 
+/*** DOCUMENTATION
+	<configInfo name="res_http_media_cache" language="en_US">
+		<synopsis>HTTP media cache</synopsis>
+		<configFile name="http_media_cache.conf">
+			<configObject name="general">
+				<synopsis>General configuration</synopsis>
+				<configOption name="timeout_secs" default="180">
+					<synopsis>The maximum time the transfer is allowed to complete in seconds. See https://curl.se/libcurl/c/CURLOPT_TIMEOUT.html for details.</synopsis>
+				</configOption>
+				<configOption name="user_agent">
+					<synopsis>The HTTP User-Agent to use for requests. See https://curl.se/libcurl/c/CURLOPT_USERAGENT.html for details.</synopsis>
+				</configOption>
+				<configOption name="follow_location" default="1">
+					<synopsis>Follow HTTP 3xx redirects on requests. See https://curl.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html for details.</synopsis>
+				</configOption>
+				<configOption name="max_redirects" default="8">
+					<synopsis>The maximum number of redirects to follow. See https://curl.se/libcurl/c/CURLOPT_MAXREDIRS.html for details.</synopsis>
+				</configOption>
+				<configOption name="proxy">
+					<synopsis>The proxy to use for requests. See https://curl.se/libcurl/c/CURLOPT_PROXY.html for details.</synopsis>
+				</configOption>
+				<configOption name="protocols">
+					<synopsis>The comma separated list of allowed protocols for the request. Available with cURL 7.85.0 or later. See https://curl.se/libcurl/c/CURLOPT_PROTOCOLS_STR.html for details.</synopsis>
+				</configOption>
+				<configOption name="redirect_protocols">
+					<synopsis>The comma separated list of allowed protocols for redirects. Available with cURL 7.85.0 or later. See https://curl.se/libcurl/c/CURLOPT_REDIR_PROTOCOLS_STR.html for details.</synopsis>
+				</configOption>
+				<configOption name="dns_cache_timeout_secs" default="60">
+					<synopsis>The life-time for DNS cache entries. See https://curl.se/libcurl/c/CURLOPT_DNS_CACHE_TIMEOUT.html for details.</synopsis>
+				</configOption>
+			</configObject>
+		</configFile>
+	</configInfo>
+***/
+
 #include "asterisk.h"
 
 #include <curl/curl.h>
@@ -44,6 +79,123 @@
 
 #define MAX_HEADER_LENGTH 1023
 
+#ifdef CURL_AT_LEAST_VERSION
+#if CURL_AT_LEAST_VERSION(7, 85, 0)
+#define AST_CURL_HAS_PROTOCOLS_STR 1
+#endif
+#endif
+
+static int http_media_cache_config_pre_apply(void);
+
+/*! \brief General configuration options for http media cache. */
+struct conf_general_options {
+	/*! \brief Request timeout to use */
+	int curl_timeout;
+
+	/*! \brief Follow 3xx redirects automatically. */
+	int curl_followlocation;
+
+	/*! \brief Number of redirects to follow for one request. */
+	int curl_maxredirs;
+
+	/*! \brief Life-time of CURL DNS cache entries. */
+	int curl_dns_cache_timeout;
+
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(curl_useragent); /*! \brief User-agent to use for requests. */
+		AST_STRING_FIELD(curl_proxy); /*! \brief Proxy to use for requests. None by default. */
+		AST_STRING_FIELD(curl_protocols); /*! \brief Allowed protocols to use for requests. All by default. */
+		AST_STRING_FIELD(curl_redir_protocols); /*! \brief Allowed protocols to use on redirect. All by default. */
+	);
+};
+
+/*! \brief All configuration options for http media cache. */
+struct conf {
+	/*! The general section configuration options. */
+	struct conf_general_options *general;
+};
+
+/*! \brief Locking container for safe configuration access. */
+static AO2_GLOBAL_OBJ_STATIC(confs);
+
+/*! \brief Mapping of the http media cache conf struct's general to the general context in the config file. */
+static struct aco_type general_option = {
+	.type = ACO_GLOBAL,
+	.name = "general",
+	.item_offset = offsetof(struct conf, general),
+	.category = "general",
+	.category_match = ACO_WHITELIST_EXACT,
+};
+
+static struct aco_type *general_options[] = ACO_TYPES(&general_option);
+
+/*! \brief Disposes of the http media cache conf object */
+static void conf_destructor(void *obj)
+{
+	struct conf *cfg = obj;
+	ast_string_field_free_memory(cfg->general);
+	ao2_cleanup(cfg->general);
+}
+
+/*! \brief Creates the http media cache conf object. */
+static void *conf_alloc(void)
+{
+	struct conf *cfg;
+
+	if (!(cfg = ao2_alloc(sizeof(*cfg), conf_destructor))) {
+		return NULL;
+	}
+
+	if (!(cfg->general = ao2_alloc(sizeof(*cfg->general), NULL))) {
+		ao2_ref(cfg, -1);
+		return NULL;
+	}
+
+	if (ast_string_field_init(cfg->general, 256)) {
+		ao2_ref(cfg, -1);
+		return NULL;
+	}
+
+	return cfg;
+}
+
+/*! \brief The conf file that's processed for the module. */
+static struct aco_file conf_file = {
+	/*! The config file name. */
+	.filename = "res_http_media_cache.conf",
+	/*! The mapping object types to be processed. */
+	.types = ACO_TYPES(&general_option),
+};
+
+CONFIG_INFO_STANDARD(cfg_info, confs, conf_alloc,
+		.pre_apply_config = http_media_cache_config_pre_apply,
+		.files = ACO_FILES(&conf_file));
+
+/*!
+ * \brief Pre-apply callback for the config framework.
+ *
+ * This validates that used options match the ones supported by CURL.
+ */
+static int http_media_cache_config_pre_apply(void)
+{
+#ifndef AST_CURL_HAS_PROTOCOLS_STR
+	struct conf *cfg = aco_pending_config(&cfg_info);
+
+	if (!ast_strlen_zero(cfg->general->curl_protocols)) {
+		ast_log(AST_LOG_ERROR, "'protocols' not supported by linked CURL library. Please recompile against newer CURL.\n");
+		return -1;
+	}
+
+	if (!ast_strlen_zero(cfg->general->curl_redir_protocols)) {
+		ast_log(AST_LOG_ERROR, "'redirect_protocols' not supported by linked CURL library. Please recompile against newer CURL.\n");
+		return -1;
+	}
+#endif
+
+	return 0;
+}
+
+
 /*! \brief Data passed to cURL callbacks */
 struct curl_bucket_file_data {
 	/*! The \c ast_bucket_file object that caused the operation */
@@ -116,7 +268,7 @@ static size_t curl_body_callback(void *ptr, size_t size, size_t nitems, void *da
 static void bucket_file_set_expiration(struct ast_bucket_file *bucket_file)
 {
 	struct ast_bucket_metadata *metadata;
-	char time_buf[32];
+	char time_buf[32], secs[AST_TIME_T_LEN];
 	struct timeval actual_expires = ast_tvnow();
 
 	metadata = ast_bucket_file_metadata_get(bucket_file, "cache-control");
@@ -150,7 +302,8 @@ static void bucket_file_set_expiration(struct ast_bucket_file *bucket_file)
 	}
 
 	/* Use 'now' if we didn't get an expiration time */
-	snprintf(time_buf, sizeof(time_buf), "%30lu", actual_expires.tv_sec);
+	ast_time_t_to_string(actual_expires.tv_sec, secs, sizeof(secs));
+	snprintf(time_buf, sizeof(time_buf), "%30s", secs);
 
 	ast_bucket_file_metadata_set(bucket_file, "__actual_expires", time_buf);
 }
@@ -169,11 +322,6 @@ static char *file_extension_from_string(const char *str, char *buffer, size_t ca
 	return NULL;
 }
 
-static char *file_extension_from_url(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)
-{
-	return file_extension_from_string(ast_sorcery_object_get_id(bucket_file), buffer, capacity);
-}
-
 /*!
  * \internal
  * \brief Normalize the value of a Content-Type header
@@ -244,6 +392,7 @@ static char *file_extension_from_content_type(struct ast_bucket_file *bucket_fil
 
 static char *file_extension_from_url_path(struct ast_bucket_file *bucket_file, char *buffer, size_t capacity)
 {
+	const char *path;
 	struct ast_uri *uri;
 
 	uri = ast_uri_parse(ast_sorcery_object_get_id(bucket_file));
@@ -253,26 +402,27 @@ static char *file_extension_from_url_path(struct ast_bucket_file *bucket_file, c
 		return NULL;
 	}
 
+	path = ast_uri_path(uri);
+	if (!path) {
+		ao2_cleanup(uri);
+		return NULL;
+	}
+
 	/* Just parse it as a string like before, but without the extra cruft */
-	buffer = file_extension_from_string(ast_uri_path(uri), buffer, capacity);
+	buffer = file_extension_from_string(path, buffer, capacity);
 	ao2_cleanup(uri);
 	return buffer;
 }
 
 static void bucket_file_set_extension(struct ast_bucket_file *bucket_file)
 {
-	/* We will attempt to determine an extension in the following order for backwards
-	 * compatibility:
-	 *
-	 * 1. Look at tail end of URL for extension
-	 * 2. Use the Content-Type header if present
-	 * 3. Parse the URL (assuming we can) and look at the tail of the path
-	 */
+	/* Using Content-Type first allows for the most flexibility for whomever
+	 * is serving up the audio file. If that doesn't turn up anything useful
+	 * we'll try to parse the URL and use the extension */
 
 	char buffer[64];
 
-	if (file_extension_from_url(bucket_file, buffer, sizeof(buffer))
-	   || file_extension_from_content_type(bucket_file, buffer, sizeof(buffer))
+	if (file_extension_from_content_type(bucket_file, buffer, sizeof(buffer))
 	   || file_extension_from_url_path(bucket_file, buffer, sizeof(buffer))) {
 		ast_bucket_file_metadata_set(bucket_file, "ext", buffer);
 	}
@@ -314,7 +464,7 @@ static int bucket_file_expired(struct ast_bucket_file *bucket_file)
 		return 1;
 	}
 
-	if (sscanf(metadata->value, "%lu", &expires.tv_sec) != 1) {
+	if ((expires.tv_sec = ast_string_to_time_t(metadata->value)) == -1) {
 		return 1;
 	}
 
@@ -326,6 +476,8 @@ static int bucket_file_expired(struct ast_bucket_file *bucket_file)
  */
 static CURL *get_curl_instance(struct curl_bucket_file_data *cb_data)
 {
+	RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup);
+	CURLcode rc;
 	CURL *curl;
 
 	curl = curl_easy_init();
@@ -334,14 +486,47 @@ static CURL *get_curl_instance(struct curl_bucket_file_data *cb_data)
 	}
 
 	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
-	curl_easy_setopt(curl, CURLOPT_TIMEOUT, 180);
 	curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_callback);
-	curl_easy_setopt(curl, CURLOPT_USERAGENT, AST_CURL_USER_AGENT);
-	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
-	curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 8);
 	curl_easy_setopt(curl, CURLOPT_URL, ast_sorcery_object_get_id(cb_data->bucket_file));
 	curl_easy_setopt(curl, CURLOPT_HEADERDATA, cb_data);
 
+	curl_easy_setopt(curl, CURLOPT_TIMEOUT, cfg->general->curl_timeout);
+	curl_easy_setopt(curl, CURLOPT_USERAGENT, cfg->general->curl_useragent);
+	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, cfg->general->curl_followlocation ? 1 : 0);
+	curl_easy_setopt(curl, CURLOPT_MAXREDIRS, cfg->general->curl_maxredirs);
+
+	if (!ast_strlen_zero(cfg->general->curl_proxy)) {
+		curl_easy_setopt(curl, CURLOPT_PROXY, cfg->general->curl_proxy);
+	}
+
+	if (!ast_strlen_zero(cfg->general->curl_protocols)) {
+#ifdef AST_CURL_HAS_PROTOCOLS_STR
+		CURLcode rc = curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR, cfg->general->curl_protocols);
+		if (rc != CURLE_OK) {
+			ast_log(AST_LOG_ERROR, "Setting protocols to '%s' failed: %d\n", cfg->general->curl_protocols, rc);
+			curl_easy_cleanup(curl);
+			return NULL;
+		}
+#endif
+	}
+	if (!ast_strlen_zero(cfg->general->curl_redir_protocols)) {
+#ifdef AST_CURL_HAS_PROTOCOLS_STR
+		CURLcode rc = curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR, cfg->general->curl_redir_protocols);
+		if (rc != CURLE_OK) {
+			ast_log(AST_LOG_ERROR, "Setting redirect_protocols to '%s' failed: %d\n", cfg->general->curl_redir_protocols, rc);
+			curl_easy_cleanup(curl);
+			return NULL;
+		}
+#endif
+	}
+
+	rc = curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, cfg->general->curl_dns_cache_timeout);
+	if (rc != CURLE_OK) {
+		ast_log(AST_LOG_ERROR, "Setting dns_cache_timeout to '%d' failed: %d\n", cfg->general->curl_dns_cache_timeout, rc);
+		curl_easy_cleanup(curl);
+		return NULL;
+	}
+
 	return curl;
 }
 
@@ -543,11 +728,73 @@ static struct ast_sorcery_wizard https_bucket_file_wizard = {
 
 static int unload_module(void)
 {
+	aco_info_destroy(&cfg_info);
+	ao2_global_obj_release(confs);
 	return 0;
 }
 
 static int load_module(void)
 {
+	if (aco_info_init(&cfg_info)) {
+		aco_info_destroy(&cfg_info);
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+
+	aco_option_register(&cfg_info, "timeout_secs", ACO_EXACT, general_options,
+			"180", OPT_INT_T, 0,
+			FLDSET(struct conf_general_options, curl_timeout));
+
+	aco_option_register(&cfg_info, "user_agent", ACO_EXACT, general_options,
+			AST_CURL_USER_AGENT, OPT_STRINGFIELD_T, 0,
+			STRFLDSET(struct conf_general_options, curl_useragent));
+
+	aco_option_register(&cfg_info, "follow_location", ACO_EXACT, general_options,
+			"yes", OPT_BOOL_T, 1,
+			FLDSET(struct conf_general_options, curl_followlocation));
+
+	aco_option_register(&cfg_info, "max_redirects", ACO_EXACT, general_options,
+			"8", OPT_INT_T, 0,
+			FLDSET(struct conf_general_options, curl_maxredirs));
+
+	aco_option_register(&cfg_info, "proxy", ACO_EXACT, general_options,
+			NULL, OPT_STRINGFIELD_T, 1,
+			STRFLDSET(struct conf_general_options, curl_proxy));
+
+	aco_option_register(&cfg_info, "dns_cache_timeout_secs", ACO_EXACT, general_options,
+			"60", OPT_INT_T, 0,
+			FLDSET(struct conf_general_options, curl_dns_cache_timeout));
+
+	aco_option_register(&cfg_info, "protocols", ACO_EXACT, general_options,
+			NULL, OPT_STRINGFIELD_T, 1,
+			STRFLDSET(struct conf_general_options, curl_protocols));
+
+	aco_option_register(&cfg_info, "redirect_protocols", ACO_EXACT, general_options,
+			NULL, OPT_STRINGFIELD_T, 1,
+			STRFLDSET(struct conf_general_options, curl_redir_protocols));
+
+
+	if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
+		struct conf *cfg;
+
+		ast_log(LOG_NOTICE, "Could not load res_http_media_cache config; using defaults\n");
+		cfg = conf_alloc();
+		if (!cfg) {
+			aco_info_destroy(&cfg_info);
+			return AST_MODULE_LOAD_DECLINE;
+		}
+
+		if (aco_set_defaults(&general_option, "general", cfg->general)) {
+			ast_log(LOG_ERROR, "Failed to initialize res_http_media_cache defaults.\n");
+			ao2_ref(cfg, -1);
+			aco_info_destroy(&cfg_info);
+			return AST_MODULE_LOAD_DECLINE;
+		}
+
+		ao2_global_obj_replace_unref(confs, cfg);
+		ao2_ref(cfg, -1);
+	}
+
 	if (ast_bucket_scheme_register("http", &http_bucket_wizard, &http_bucket_file_wizard,
 			NULL, NULL)) {
 		ast_log(LOG_ERROR, "Failed to register Bucket HTTP wizard scheme implementation\n");
diff --git a/res/res_http_websocket.c b/res/res_http_websocket.c
index d0a5ea72e2a0260131e11a41bc51807daa272ad5..f8da4878284fcd22bfac2c2af08ad1e5ed1ae75a 100644
--- a/res/res_http_websocket.c
+++ b/res/res_http_websocket.c
@@ -1308,7 +1308,11 @@ static enum ast_websocket_result websocket_client_handshake_get_response(
 	int has_accept = 0;
 	int has_protocol = 0;
 
-	if (ast_iostream_gets(client->ser->stream, buf, sizeof(buf)) <= 0) {
+	while (ast_iostream_gets(client->ser->stream, buf, sizeof(buf)) <= 0) {
+		if (errno == EINTR || errno == EAGAIN) {
+			continue;
+		}
+
 		ast_log(LOG_ERROR, "Unable to retrieve HTTP status line.");
 		return WS_BAD_STATUS;
 	}
@@ -1321,10 +1325,19 @@ static enum ast_websocket_result websocket_client_handshake_get_response(
 
 	/* Ignoring line folding - assuming header field values are contained
 	   within a single line */
-	while (ast_iostream_gets(client->ser->stream, buf, sizeof(buf)) > 0) {
+	while (1) {
+		ssize_t len = ast_iostream_gets(client->ser->stream, buf, sizeof(buf));
 		char *name, *value;
-		int parsed = ast_http_header_parse(buf, &name, &value);
+		int parsed;
+
+		if (len <= 0) {
+			if (errno == EINTR || errno == EAGAIN) {
+				continue;
+			}
+			break;
+		}
 
+		parsed = ast_http_header_parse(buf, &name, &value);
 		if (parsed < 0) {
 			break;
 		}
@@ -1360,6 +1373,7 @@ static enum ast_websocket_result websocket_client_handshake_get_response(
 			return WS_NOT_SUPPORTED;
 		}
 	}
+
 	return has_upgrade && has_connection && has_accept ?
 		WS_OK : WS_HEADER_MISSING;
 }
diff --git a/res/res_monitor.c b/res/res_monitor.c
index aba4bbe6e3580870015479f2f2c7c30b7e564f6d..1264ad0a3356c5d375e2cd37cbdcaaebd50bc9bc 100644
--- a/res/res_monitor.c
+++ b/res/res_monitor.c
@@ -25,6 +25,7 @@
 
 /*** MODULEINFO
 	<use type="module">func_periodic_hook</use>
+	<defaultenabled>no</defaultenabled>
 	<support_level>deprecated</support_level>
 	<replacement>app_mixmonitor</replacement>
 	<deprecated_in>16</deprecated_in>
diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c
index 9844de0cd0323ab3f2ebb8b1c8a62b485a9cb50f..6196211265e67a9c72d607c420c8461d3664c189 100644
--- a/res/res_musiconhold.c
+++ b/res/res_musiconhold.c
@@ -193,6 +193,8 @@ struct mohclass {
 	unsigned int delete:1;
 	AST_LIST_HEAD_NOLOCK(, mohdata) members;
 	AST_LIST_ENTRY(mohclass) list;
+	/*!< Play the moh if the channel answered */
+	int answeredonly;
 };
 
 struct mohdata {
@@ -1193,6 +1195,8 @@ static void moh_parse_options(struct ast_variable *var, struct mohclass *mohclas
 				ast_log(LOG_WARNING, "kill_method '%s' is invalid.  Setting to 'process_group'\n", var->value);
 				mohclass->kill_method = KILL_METHOD_PROCESS_GROUP;
 			}
+		} else if (!strcasecmp(var->name, "answeredonly")) {
+			mohclass->answeredonly = ast_true(var->value) ? 1: 0;
 		}
 	}
 
@@ -1835,6 +1839,11 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
 		return -1;
 	}
 
+	if (mohclass->answeredonly && (ast_channel_state(chan) != AST_STATE_UP)) {
+		ast_verb(3, "The channel '%s' is not answered yet. Ignore the moh request.\n", ast_channel_name(chan));
+		return -1;
+	}
+
 	/* If we are using a cached realtime class with files, re-scan the files */
 	if (!var && ast_test_flag(global_flags, MOH_CACHERTCLASSES) && mohclass->realtime && !strcasecmp(mohclass->mode, "files")) {
 		/*
diff --git a/res/res_mutestream.c b/res/res_mutestream.c
index 8040a3ac9bdbea36f18ecf3c5e14829c1af4f82e..a09c83c36c9db494f73b719843399c42765a2982 100644
--- a/res/res_mutestream.c
+++ b/res/res_mutestream.c
@@ -26,7 +26,7 @@
  *
  * \note This module only handles audio streams today, but can easily be appended to also
  * zero out text streams if there's an application for it.
- * When we know and understands what happens if we zero out video, we can do that too.
+ * When we know and understand what happens if we zero out video, we can do that too.
  */
 
 /*** MODULEINFO
@@ -69,16 +69,13 @@
 			</parameter>
 		</syntax>
 		<description>
-			<para>The MUTEAUDIO function can be used to mute inbound (to the PBX) or outbound audio in a call.
-			</para>
-			<para>Examples:
-			</para>
-			<para>
-			MUTEAUDIO(in)=on
-			</para>
-			<para>
-			MUTEAUDIO(in)=off
-			</para>
+			<para>The MUTEAUDIO function can be used to mute inbound (to the PBX) or outbound audio in a call.</para>
+			<example title="Mute incoming audio">
+			exten => s,1,Set(MUTEAUDIO(in)=on)
+			</example>
+			<example title="Do not mute incoming audio">
+			exten => s,1,Set(MUTEAUDIO(in)=off)
+			</example>
 		</description>
 	</function>
 	<manager name="MuteAudio" language="en_US">
diff --git a/res/res_odbc.c b/res/res_odbc.c
index 63fdf37c087e251e58d1c90ffcbcf8d9507de45f..54037f9ff0d4c78906e00fd69cadb443513b27ce 100644
--- a/res/res_odbc.c
+++ b/res/res_odbc.c
@@ -1029,7 +1029,9 @@ static odbc_status odbc_obj_connect(struct odbc_obj *obj)
 	/* Dont connect while server is marked as unreachable via negative_connection_cache */
 	negative_cache_expiration = obj->parent->last_negative_connect.tv_sec + obj->parent->negative_connection_cache.tv_sec;
 	if (time(NULL) < negative_cache_expiration) {
-		ast_log(LOG_WARNING, "Not connecting to %s. Negative connection cache for %ld seconds\n", obj->parent->name, negative_cache_expiration - time(NULL));
+		char secs[AST_TIME_T_LEN];
+		ast_time_t_to_string(negative_cache_expiration - time(NULL), secs, sizeof(secs));
+		ast_log(LOG_WARNING, "Not connecting to %s. Negative connection cache for %s seconds\n", obj->parent->name, secs);
 		return ODBC_FAIL;
 	}
 
diff --git a/res/res_phoneprov.c b/res/res_phoneprov.c
index a050047a06b35e382d7d22ca4bbc37fb7c745166..bff496ec0af293281d9de9fac3446a2a22cbb425 100644
--- a/res/res_phoneprov.c
+++ b/res/res_phoneprov.c
@@ -874,6 +874,8 @@ static int phoneprov_callback(struct ast_tcptls_session_instance *ser, const str
 	char path[PATH_MAX];
 	char *file = NULL;
 	char *server;
+	char *newserver = NULL;
+	struct extension *exten_iter;
 	int len;
 	int fd;
 	struct ast_str *http_header;
@@ -955,8 +957,7 @@ static int phoneprov_callback(struct ast_tcptls_session_instance *ser, const str
 			if ((res = getsockname(ast_iostream_get_fd(ser->stream), &name.sa, &namelen))) {
 				ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
 			} else {
-				struct extension *exten_iter;
-				const char *newserver = ast_inet_ntoa(name.sa_in.sin_addr);
+				newserver = ast_strdupa(ast_inet_ntoa(name.sa_in.sin_addr));
 
 				AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
 					AST_VAR_LIST_INSERT_TAIL(exten_iter->headp,
@@ -967,6 +968,21 @@ static int phoneprov_callback(struct ast_tcptls_session_instance *ser, const str
 
 		ast_str_substitute_variables_varshead(&tmp, 0, AST_LIST_FIRST(&route->user->extensions)->headp, file);
 
+		/* Do not retain dynamic SERVER address because next request from the phone might arrive on
+		 * different interface IP address eg. if this is a multi-homed server on multiple subnets */
+		if (newserver) {
+			struct ast_var_t *varns;
+			AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
+				AST_LIST_TRAVERSE_SAFE_BEGIN(exten_iter->headp, varns, entries) {
+					if (!strcmp(variable_lookup[AST_PHONEPROV_STD_SERVER], ast_var_name(varns))) {
+						AST_LIST_REMOVE_CURRENT(entries);
+						ast_var_delete(varns);
+					}
+				}
+				AST_LIST_TRAVERSE_SAFE_END
+			}
+		}
+
 		ast_free(file);
 
 		http_header = ast_str_create(80);
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 165cc7893fc3bf0d15b448a8c57993ab30d1f327..cf7ceea5c171a71f21ab9914fbf141570733fd04 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -40,11 +40,14 @@
 #include "asterisk/uuid.h"
 #include "asterisk/sorcery.h"
 #include "asterisk/file.h"
+#include "asterisk/causes.h"
 #include "asterisk/cli.h"
+#include "asterisk/callerid.h"
 #include "asterisk/res_pjsip_cli.h"
 #include "asterisk/test.h"
 #include "asterisk/res_pjsip_presence_xml.h"
 #include "asterisk/res_pjproject.h"
+#include "asterisk/utf8.h"
 
 /*** MODULEINFO
 	<depend>pjproject</depend>
@@ -53,6 +56,7 @@
 	<depend>res_sorcery_memory</depend>
 	<depend>res_sorcery_astdb</depend>
 	<use type="module">res_statsd</use>
+	<use type="module">res_geolocation</use>
 	<support_level>core</support_level>
  ***/
 
@@ -906,6 +910,7 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint,
 	/* Add the user=phone parameter if applicable */
 	ast_sip_add_usereqphone(endpoint, dlg->pool, dlg->target);
 	ast_sip_add_usereqphone(endpoint, dlg->pool, dlg->remote.info->uri);
+	ast_sip_add_usereqphone(endpoint, dlg->pool, dlg->local.info->uri);
 
 	if (!ast_strlen_zero(outbound_proxy)) {
 		pjsip_route_hdr route_set, *route;
@@ -1872,6 +1877,22 @@ int ast_sip_add_header(pjsip_tx_data *tdata, const char *name, const char *value
 	return 0;
 }
 
+pjsip_generic_string_hdr *ast_sip_add_header2(pjsip_tx_data *tdata,
+	const char *name, const char *value)
+{
+	pj_str_t hdr_name;
+	pj_str_t hdr_value;
+	pjsip_generic_string_hdr *hdr;
+
+	pj_cstr(&hdr_name, name);
+	pj_cstr(&hdr_value, value);
+
+	hdr = pjsip_generic_string_hdr_create(tdata->pool, &hdr_name, &hdr_value);
+
+	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) hdr);
+	return hdr;
+}
+
 static pjsip_msg_body *ast_body_to_pjsip_body(pj_pool_t *pool, const struct ast_sip_body *body)
 {
 	pj_str_t type;
@@ -2242,31 +2263,62 @@ int ast_sip_send_response(pjsip_response_addr *res_addr, pjsip_tx_data *tdata, s
 	return status == PJ_SUCCESS ? 0 : -1;
 }
 
+static void pool_destroy_callback(void *arg)
+{
+	pj_pool_t *pool = (pj_pool_t *)arg;
+	pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+}
+
+static void clean_contact_from_tdata(pjsip_tx_data *tdata)
+{
+	struct ast_sip_contact *contact;
+	contact = ast_sip_mod_data_get(tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT);
+	ao2_cleanup(contact);
+	ast_sip_mod_data_set(tdata->pool, tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT, NULL);
+	pjsip_tx_data_dec_ref(tdata);
+}
+
 int ast_sip_send_stateful_response(pjsip_rx_data *rdata, pjsip_tx_data *tdata, struct ast_sip_endpoint *sip_endpoint)
 {
 	pjsip_transaction *tsx;
+	pj_grp_lock_t *tsx_glock;
+	pj_pool_t *pool;
 
-	if (pjsip_tsx_create_uas(NULL, rdata, &tsx) != PJ_SUCCESS) {
-		struct ast_sip_contact *contact;
-
+	/* Create and initialize global lock pool */
+	pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "stateful response", PJSIP_POOL_TSX_LEN, PJSIP_POOL_TSX_INC);
+	if (!pool){
 		/* ast_sip_create_response bumps the refcount of the contact and adds it to the tdata.
 		 * We'll leak that reference if we don't get rid of it here.
 		 */
-		contact = ast_sip_mod_data_get(tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT);
-		ao2_cleanup(contact);
-		ast_sip_mod_data_set(tdata->pool, tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT, NULL);
-		pjsip_tx_data_dec_ref(tdata);
+		clean_contact_from_tdata(tdata);
+		return -1;
+	}
+	/* Create with handler so that we can release the pool once the glock derefs out */
+	if(pj_grp_lock_create_w_handler(pool, NULL, pool, &pool_destroy_callback, &tsx_glock) != PJ_SUCCESS) {
+		clean_contact_from_tdata(tdata);
+		pool_destroy_callback((void *) pool);
+		return -1;
+	}
+	/* We need an additional reference as the qualify thread may destroy this out
+	 * from under us. Add it now before it gets added to the tsx. */
+	pj_grp_lock_add_ref(tsx_glock);
+
+	if (pjsip_tsx_create_uas2(NULL, rdata, tsx_glock, &tsx) != PJ_SUCCESS) {
+		clean_contact_from_tdata(tdata);
+		pj_grp_lock_dec_ref(tsx_glock);
 		return -1;
 	}
-	pjsip_tsx_recv_msg(tsx, rdata);
 
+	pjsip_tsx_recv_msg(tsx, rdata);
 	supplement_outgoing_response(tdata, sip_endpoint);
 
 	if (pjsip_tsx_send_msg(tsx, tdata) != PJ_SUCCESS) {
+		pj_grp_lock_dec_ref(tsx_glock);
 		pjsip_tx_data_dec_ref(tdata);
 		return -1;
 	}
 
+	pj_grp_lock_dec_ref(tsx_glock);
 	return 0;
 }
 
@@ -2402,6 +2454,249 @@ int ast_sip_call_codec_str_to_pref(struct ast_flags *pref, const char *pref_str,
 	return 0;
 }
 
+/*!
+ * \internal
+ * \brief Set an ast_party_id name and number based on an identity header.
+ * \param hdr From, P-Asserted-Identity, or Remote-Party-ID header on incoming message
+ * \param[out] id The ID to set data on
+ */
+static void set_id_from_hdr(pjsip_fromto_hdr *hdr, struct ast_party_id *id)
+{
+	char cid_name[AST_CHANNEL_NAME];
+	char cid_num[AST_CHANNEL_NAME];
+	size_t cid_name_size = AST_CHANNEL_NAME;
+	pjsip_name_addr *id_name_addr = (pjsip_name_addr *) hdr->uri;
+	char *semi;
+	enum ast_utf8_replace_result result;
+
+	ast_copy_pj_str(cid_num, ast_sip_pjsip_uri_get_username(hdr->uri), sizeof(cid_num));
+	/* Always truncate caller-id number at a semicolon. */
+	semi = strchr(cid_num, ';');
+	if (semi) {
+		/*
+		 * We need to be able to handle URI's looking like
+		 * "sip:1235557890;phone-context=national@x.x.x.x;user=phone"
+		 *
+		 * Where the uri->user field will result in:
+		 * "1235557890;phone-context=national"
+		 *
+		 * People don't care about anything after the semicolon
+		 * showing up on their displays even though the RFC
+		 * allows the semicolon.
+		 */
+		*semi = '\0';
+	}
+
+	/*
+	 * It's safe to pass a NULL or empty string as the source.
+	 * The result will be an empty string assuming the destination
+	 * size was at least 1.
+	 */
+	result = ast_utf8_replace_invalid_chars(cid_name, &cid_name_size,
+		id_name_addr->display.ptr, id_name_addr->display.slen);
+
+	if (result != AST_UTF8_REPLACE_VALID) {
+		ast_log(LOG_WARNING, "CallerID Name '" PJSTR_PRINTF_SPEC
+			"' for number '%s' has invalid UTF-8 characters which "
+			"were replaced",
+			PJSTR_PRINTF_VAR(id_name_addr->display), cid_num);
+	}
+
+	ast_free(id->name.str);
+	id->name.str = ast_strdup(cid_name);
+	if (!ast_strlen_zero(cid_name)) {
+		id->name.valid = 1;
+	}
+	ast_free(id->number.str);
+	id->number.str = ast_strdup(cid_num);
+	if (!ast_strlen_zero(cid_num)) {
+		id->number.valid = 1;
+	}
+}
+
+/*!
+ * \internal
+ * \brief Get a P-Asserted-Identity or Remote-Party-ID header from an incoming message
+ *
+ * This function will parse the header as if it were a From header. This allows for us
+ * to easily manipulate the URI, as well as add, modify, or remove parameters from the
+ * header
+ *
+ * \param rdata The incoming message
+ * \param header_name The name of the ID header to find
+ * \retval NULL No ID header present or unable to parse ID header
+ * \retval non-NULL The parsed ID header
+ */
+static pjsip_fromto_hdr *get_id_header(pjsip_rx_data *rdata, const pj_str_t *header_name)
+{
+	static const pj_str_t from = { "From", 4 };
+	pj_str_t header_content;
+	pjsip_fromto_hdr *parsed_hdr;
+	pjsip_generic_string_hdr *ident = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
+			header_name, NULL);
+	int parsed_len;
+
+	if (!ident) {
+		return NULL;
+	}
+
+	pj_strdup_with_null(rdata->tp_info.pool, &header_content, &ident->hvalue);
+
+	parsed_hdr = pjsip_parse_hdr(rdata->tp_info.pool, &from, header_content.ptr,
+			pj_strlen(&header_content), &parsed_len);
+
+	if (!parsed_hdr) {
+		return NULL;
+	}
+
+	return parsed_hdr;
+}
+
+/*!
+ * \internal
+ * \brief Set an ast_party_id structure based on data in a P-Asserted-Identity header
+ *
+ * This makes use of \ref set_id_from_hdr for setting name and number. It uses
+ * the contents of a Privacy header in order to set presentation information.
+ *
+ * \param rdata The incoming message
+ * \param[out] id The ID to set
+ * \retval 0 Successfully set the party ID
+ * \retval non-zero Could not set the party ID
+ */
+static int set_id_from_pai(pjsip_rx_data *rdata, struct ast_party_id *id)
+{
+	static const pj_str_t pai_str = { "P-Asserted-Identity", 19 };
+	static const pj_str_t privacy_str = { "Privacy", 7 };
+	pjsip_fromto_hdr *pai_hdr = get_id_header(rdata, &pai_str);
+	pjsip_generic_string_hdr *privacy;
+
+	if (!pai_hdr) {
+		return -1;
+	}
+
+	set_id_from_hdr(pai_hdr, id);
+
+	if (!id->number.valid) {
+		return -1;
+	}
+
+	privacy = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &privacy_str, NULL);
+	if (!privacy || !pj_stricmp2(&privacy->hvalue, "none")) {
+		id->number.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
+		id->name.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
+	} else {
+		id->number.presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED;
+		id->name.presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED;
+	}
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Set an ast_party_id structure based on data in a Remote-Party-ID header
+ *
+ * This makes use of \ref set_id_from_hdr for setting name and number. It uses
+ * the privacy and screen parameters in order to set presentation information.
+ *
+ * \param rdata The incoming message
+ * \param[out] id The ID to set
+ * \retval 0 Succesfully set the party ID
+ * \retval non-zero Could not set the party ID
+ */
+static int set_id_from_rpid(pjsip_rx_data *rdata, struct ast_party_id *id)
+{
+	static const pj_str_t rpid_str = { "Remote-Party-ID", 15 };
+	static const pj_str_t privacy_str = { "privacy", 7 };
+	static const pj_str_t screen_str = { "screen", 6 };
+	pjsip_fromto_hdr *rpid_hdr = get_id_header(rdata, &rpid_str);
+	pjsip_param *screen;
+	pjsip_param *privacy;
+
+	if (!rpid_hdr) {
+		return -1;
+	}
+
+	set_id_from_hdr(rpid_hdr, id);
+
+	if (!id->number.valid) {
+		return -1;
+	}
+
+	privacy = pjsip_param_find(&rpid_hdr->other_param, &privacy_str);
+	screen = pjsip_param_find(&rpid_hdr->other_param, &screen_str);
+	if (privacy && !pj_stricmp2(&privacy->value, "full")) {
+		id->number.presentation = AST_PRES_RESTRICTED;
+		id->name.presentation = AST_PRES_RESTRICTED;
+	} else {
+		id->number.presentation = AST_PRES_ALLOWED;
+		id->name.presentation = AST_PRES_ALLOWED;
+	}
+	if (screen && !pj_stricmp2(&screen->value, "yes")) {
+		id->number.presentation |= AST_PRES_USER_NUMBER_PASSED_SCREEN;
+		id->name.presentation |= AST_PRES_USER_NUMBER_PASSED_SCREEN;
+	} else {
+		id->number.presentation |= AST_PRES_USER_NUMBER_UNSCREENED;
+		id->name.presentation |= AST_PRES_USER_NUMBER_UNSCREENED;
+	}
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Set an ast_party_id structure based on data in a From
+ *
+ * This makes use of \ref set_id_from_hdr for setting name and number. It uses
+ * no information from the message in order to set privacy. It relies on endpoint
+ * configuration for privacy information.
+ *
+ * \param rdata The incoming message
+ * \param[out] id The ID to set
+ * \retval 0 Succesfully set the party ID
+ * \retval non-zero Could not set the party ID
+ */
+static int set_id_from_from(struct pjsip_rx_data *rdata, struct ast_party_id *id)
+{
+	pjsip_fromto_hdr *from = pjsip_msg_find_hdr(rdata->msg_info.msg,
+			PJSIP_H_FROM, rdata->msg_info.msg->hdr.next);
+
+	if (!from) {
+		/* This had better not happen */
+		return -1;
+	}
+
+	set_id_from_hdr(from, id);
+
+	if (!id->number.valid) {
+		return -1;
+	}
+
+	return 0;
+}
+
+int ast_sip_set_id_connected_line(struct pjsip_rx_data *rdata, struct ast_party_id *id)
+{
+	return !set_id_from_pai(rdata, id) || !set_id_from_rpid(rdata, id) ? 0 : -1;
+}
+
+int ast_sip_set_id_from_invite(struct pjsip_rx_data *rdata, struct ast_party_id *id, struct ast_party_id *default_id, int trust_inbound)
+{
+	if (trust_inbound && (!set_id_from_pai(rdata, id) || !set_id_from_rpid(rdata, id))) {
+		/* Trusted: Check PAI and RPID */
+		ast_free(id->tag);
+		id->tag = ast_strdup(default_id->tag);
+		return 0;
+	}
+	/* Not trusted: check the endpoint config or use From. */
+	ast_party_id_copy(id, default_id);
+	if (!default_id->number.valid) {
+		set_id_from_from(rdata, id);
+	}
+	return 0;
+}
+
 /*!
  * \brief Set name and number information on an identity header.
  *
@@ -2457,6 +2752,160 @@ struct ast_threadpool *ast_sip_threadpool(void)
 	return sip_threadpool;
 }
 
+int ast_sip_is_uri_sip_sips(pjsip_uri *uri)
+{
+	return (PJSIP_URI_SCHEME_IS_SIP(uri) || PJSIP_URI_SCHEME_IS_SIPS(uri));
+}
+
+int ast_sip_is_allowed_uri(pjsip_uri *uri)
+{
+	return (ast_sip_is_uri_sip_sips(uri) || PJSIP_URI_SCHEME_IS_TEL(uri));
+}
+
+const pj_str_t *ast_sip_pjsip_uri_get_username(pjsip_uri *uri)
+{
+	if (ast_sip_is_uri_sip_sips(uri)) {
+		pjsip_sip_uri *sip_uri = pjsip_uri_get_uri(uri);
+		if (!sip_uri) {
+			return &AST_PJ_STR_EMPTY;
+		}
+		return &sip_uri->user;
+	} else if (PJSIP_URI_SCHEME_IS_TEL(uri)) {
+		pjsip_tel_uri *tel_uri = pjsip_uri_get_uri(uri);
+		if (!tel_uri) {
+			return &AST_PJ_STR_EMPTY;
+		}
+		return &tel_uri->number;
+	}
+
+	return &AST_PJ_STR_EMPTY;
+}
+
+const pj_str_t *ast_sip_pjsip_uri_get_hostname(pjsip_uri *uri)
+{
+	if (ast_sip_is_uri_sip_sips(uri)) {
+		pjsip_sip_uri *sip_uri = pjsip_uri_get_uri(uri);
+		if (!sip_uri) {
+			return &AST_PJ_STR_EMPTY;
+		}
+		return &sip_uri->host;
+	} else if (PJSIP_URI_SCHEME_IS_TEL(uri)) {
+		return &AST_PJ_STR_EMPTY;
+	}
+
+	return &AST_PJ_STR_EMPTY;
+}
+
+struct pjsip_param *ast_sip_pjsip_uri_get_other_param(pjsip_uri *uri, const pj_str_t *param_str)
+{
+	if (ast_sip_is_uri_sip_sips(uri)) {
+		pjsip_sip_uri *sip_uri = pjsip_uri_get_uri(uri);
+		if (!sip_uri) {
+			return NULL;
+		}
+		return pjsip_param_find(&sip_uri->other_param, param_str);
+	} else if (PJSIP_URI_SCHEME_IS_TEL(uri)) {
+		pjsip_tel_uri *tel_uri = pjsip_uri_get_uri(uri);
+		if (!tel_uri) {
+			return NULL;
+		}
+		return pjsip_param_find(&tel_uri->other_param, param_str);
+	}
+
+	return NULL;
+}
+
+/*! \brief Convert SIP hangup causes to Asterisk hangup causes */
+const int ast_sip_hangup_sip2cause(int cause)
+{
+	/* Possible values taken from causes.h */
+
+	switch(cause) {
+	case 401:       /* Unauthorized */
+		return AST_CAUSE_CALL_REJECTED;
+	case 403:       /* Not found */
+		return AST_CAUSE_CALL_REJECTED;
+	case 404:       /* Not found */
+		return AST_CAUSE_UNALLOCATED;
+	case 405:       /* Method not allowed */
+		return AST_CAUSE_INTERWORKING;
+	case 407:       /* Proxy authentication required */
+		return AST_CAUSE_CALL_REJECTED;
+	case 408:       /* No reaction */
+		return AST_CAUSE_NO_USER_RESPONSE;
+	case 409:       /* Conflict */
+		return AST_CAUSE_NORMAL_TEMPORARY_FAILURE;
+	case 410:       /* Gone */
+		return AST_CAUSE_NUMBER_CHANGED;
+	case 411:       /* Length required */
+		return AST_CAUSE_INTERWORKING;
+	case 413:       /* Request entity too large */
+		return AST_CAUSE_INTERWORKING;
+	case 414:       /* Request URI too large */
+		return AST_CAUSE_INTERWORKING;
+	case 415:       /* Unsupported media type */
+		return AST_CAUSE_INTERWORKING;
+	case 420:       /* Bad extension */
+		return AST_CAUSE_NO_ROUTE_DESTINATION;
+	case 480:       /* No answer */
+		return AST_CAUSE_NO_ANSWER;
+	case 481:       /* No answer */
+		return AST_CAUSE_INTERWORKING;
+	case 482:       /* Loop detected */
+		return AST_CAUSE_INTERWORKING;
+	case 483:       /* Too many hops */
+		return AST_CAUSE_NO_ANSWER;
+	case 484:       /* Address incomplete */
+		return AST_CAUSE_INVALID_NUMBER_FORMAT;
+	case 485:       /* Ambiguous */
+		return AST_CAUSE_UNALLOCATED;
+	case 486:       /* Busy everywhere */
+		return AST_CAUSE_BUSY;
+	case 487:       /* Request terminated */
+		return AST_CAUSE_INTERWORKING;
+	case 488:       /* No codecs approved */
+		return AST_CAUSE_BEARERCAPABILITY_NOTAVAIL;
+	case 491:       /* Request pending */
+		return AST_CAUSE_INTERWORKING;
+	case 493:       /* Undecipherable */
+		return AST_CAUSE_INTERWORKING;
+	case 500:       /* Server internal failure */
+		return AST_CAUSE_FAILURE;
+	case 501:       /* Call rejected */
+		return AST_CAUSE_FACILITY_REJECTED;
+	case 502:
+		return AST_CAUSE_DESTINATION_OUT_OF_ORDER;
+	case 503:       /* Service unavailable */
+		return AST_CAUSE_CONGESTION;
+	case 504:       /* Gateway timeout */
+		return AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE;
+	case 505:       /* SIP version not supported */
+		return AST_CAUSE_INTERWORKING;
+	case 600:       /* Busy everywhere */
+		return AST_CAUSE_USER_BUSY;
+	case 603:       /* Decline */
+		return AST_CAUSE_CALL_REJECTED;
+	case 604:       /* Does not exist anywhere */
+		return AST_CAUSE_UNALLOCATED;
+	case 606:       /* Not acceptable */
+		return AST_CAUSE_BEARERCAPABILITY_NOTAVAIL;
+	default:
+		if (cause < 500 && cause >= 400) {
+			/* 4xx class error that is unknown - someting wrong with our request */
+			return AST_CAUSE_INTERWORKING;
+		} else if (cause < 600 && cause >= 500) {
+			/* 5xx class error - problem in the remote end */
+			return AST_CAUSE_CONGESTION;
+		} else if (cause < 700 && cause >= 600) {
+			/* 6xx - global errors in the 4xx class */
+			return AST_CAUSE_INTERWORKING;
+		}
+		return AST_CAUSE_NORMAL;
+	}
+	/* Never reached */
+	return 0;
+}
+
 #ifdef TEST_FRAMEWORK
 AST_TEST_DEFINE(xml_sanitization_end_null)
 {
@@ -2751,6 +3200,14 @@ static int load_module(void)
 		goto error;
 	}
 
+	/*
+	 * It is OK to prune the contacts now that
+	 * ast_res_pjsip_init_options_handling() has added the contact observer
+	 * of res/res_pjsip/pjsip_options.c to sorcery (to ensure that any
+	 * pruned contacts are removed from this module's data structure).
+	 */
+	ast_sip_location_prune_boot_contacts();
+
 	if (ast_res_pjsip_init_message_ip_updater()) {
 		ast_log(LOG_ERROR, "Failed to initialize message IP updating. Aborting load\n");
 		goto error;
@@ -2812,5 +3269,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_
 	.reload = reload_module,
 	.load_pri = AST_MODPRI_CHANNEL_DEPEND - 5,
 	.requires = "dnsmgr,res_pjproject,res_sorcery_config,res_sorcery_memory,res_sorcery_astdb",
-	.optional_modules = "res_statsd",
+	.optional_modules = "res_geolocation,res_statsd",
 );
diff --git a/res/res_pjsip/config_global.c b/res/res_pjsip/config_global.c
index ce2146a44d590d1b8fd77af59f15e2f5ef5a106d..8fef34e252a2a69b8bbc90caa941e9f140223357 100644
--- a/res/res_pjsip/config_global.c
+++ b/res/res_pjsip/config_global.c
@@ -48,12 +48,14 @@
 #define DEFAULT_MWI_TPS_QUEUE_HIGH AST_TASKPROCESSOR_HIGH_WATER_LEVEL
 #define DEFAULT_MWI_TPS_QUEUE_LOW -1
 #define DEFAULT_MWI_DISABLE_INITIAL_UNSOLICITED 0
+#define DEFAULT_ALLOW_SENDING_180_AFTER_183 0
 #define DEFAULT_IGNORE_URI_USER_OPTIONS 0
 #define DEFAULT_USE_CALLERID_CONTACT 0
 #define DEFAULT_ACCEPT_PROXY_REQ_ONLY 0
 #define DEFAULT_SEND_CONTACT_STATUS_ON_UPDATE_REGISTRATION 0
 #define DEFAULT_TASKPROCESSOR_OVERLOAD_TRIGGER TASKPROCESSOR_OVERLOAD_TRIGGER_GLOBAL
 #define DEFAULT_NOREFERSUB 1
+#define DEFAULT_ALL_CODECS_ON_EMPTY_REINVITE 0
 
 /*!
  * \brief Cached global config object
@@ -93,6 +95,8 @@ struct global_config {
 	unsigned int contact_expiration_check_interval;
 	/*! Nonzero to disable multi domain support */
 	unsigned int disable_multi_domain;
+	/*! Nonzero to disable changing 180/SDP to 183/SDP */
+	unsigned int allow_sending_180_after_183;
 	/*! The maximum number of unidentified requests per source IP address before a security event is logged */
 	unsigned int unidentified_request_count;
 	/*! The period during which unidentified requests are accumulated */
@@ -118,6 +122,8 @@ struct global_config {
 	/*! Nonzero if norefersub is to be sent in Supported header */
 	unsigned int norefersub;
 	unsigned int accept_proxy_req_only;
+	/*! Nonzero if we should return all codecs on empty re-INVITE */
+	unsigned int all_codecs_on_empty_reinvite;
 };
 
 static void global_destructor(void *obj)
@@ -446,6 +452,21 @@ unsigned int ast_sip_get_mwi_disable_initial_unsolicited(void)
 	return disable_initial_unsolicited;
 }
 
+unsigned int ast_sip_get_allow_sending_180_after_183(void)
+{
+	unsigned int allow_sending_180_after_183;
+	struct global_config *cfg;
+
+	cfg = get_global_cfg();
+	if (!cfg) {
+		return DEFAULT_ALLOW_SENDING_180_AFTER_183;
+	}
+
+	allow_sending_180_after_183 = cfg->allow_sending_180_after_183;
+	ao2_ref(cfg, -1);
+	return allow_sending_180_after_183;
+}
+
 unsigned int ast_sip_get_ignore_uri_user_options(void)
 {
 	unsigned int ignore_uri_user_options;
@@ -536,6 +557,21 @@ unsigned int ast_sip_get_norefersub(void)
 	return norefersub;
 }
 
+unsigned int ast_sip_get_all_codecs_on_empty_reinvite(void)
+{
+	unsigned int all_codecs_on_empty_reinvite;
+	struct global_config *cfg;
+
+	cfg = get_global_cfg();
+	if (!cfg) {
+		return DEFAULT_ALL_CODECS_ON_EMPTY_REINVITE;
+	}
+
+	all_codecs_on_empty_reinvite = cfg->all_codecs_on_empty_reinvite;
+	ao2_ref(cfg, -1);
+	return all_codecs_on_empty_reinvite;
+}
+
 static int overload_trigger_handler(const struct aco_option *opt,
 	struct ast_variable *var, void *obj)
 {
@@ -725,6 +761,9 @@ int ast_sip_initialize_sorcery_global(void)
 	ast_sorcery_object_field_register(sorcery, "global", "mwi_disable_initial_unsolicited",
 		DEFAULT_MWI_DISABLE_INITIAL_UNSOLICITED ? "yes" : "no",
 		OPT_BOOL_T, 1, FLDSET(struct global_config, mwi.disable_initial_unsolicited));
+	ast_sorcery_object_field_register(sorcery, "global", "allow_sending_180_after_183",
+		DEFAULT_ALLOW_SENDING_180_AFTER_183 ? "yes" : "no",
+		OPT_BOOL_T, 1, FLDSET(struct global_config, allow_sending_180_after_183));
 	ast_sorcery_object_field_register(sorcery, "global", "ignore_uri_user_options",
 		DEFAULT_IGNORE_URI_USER_OPTIONS ? "yes" : "no",
 		OPT_BOOL_T, 1, FLDSET(struct global_config, ignore_uri_user_options));
@@ -742,6 +781,9 @@ int ast_sip_initialize_sorcery_global(void)
 		OPT_YESNO_T, 1, FLDSET(struct global_config, norefersub));
 	ast_sorcery_object_field_register(sorcery, "global", "accept_proxy_req_only",
 		DEFAULT_ACCEPT_PROXY_REQ_ONLY, OPT_UINT_T, 0, FLDSET(struct global_config, accept_proxy_req_only));
+	ast_sorcery_object_field_register(sorcery, "global", "all_codecs_on_empty_reinvite",
+		DEFAULT_ALL_CODECS_ON_EMPTY_REINVITE ? "yes" : "no",
+		OPT_BOOL_T, 1, FLDSET(struct global_config, all_codecs_on_empty_reinvite));
 
 	if (ast_sorcery_instance_observer_add(sorcery, &observer_callbacks_global)) {
 		return -1;
diff --git a/res/res_pjsip/config_transport.c b/res/res_pjsip/config_transport.c
index d0a28cd04c82a194dfeb2581c97eaff6e7abe583..c8655c2eea62059cb181c852ba402a351212ef36 100644
--- a/res/res_pjsip/config_transport.c
+++ b/res/res_pjsip/config_transport.c
@@ -577,6 +577,22 @@ static void copy_state_to_transport(struct ast_sip_transport *transport)
 	memcpy(&transport->external_address, &transport->state->external_signaling_address, sizeof(transport->external_signaling_address));
 }
 
+#ifdef HAVE_PJSIP_TLS_TRANSPORT_RESTART
+static int file_stat_cmp(const struct stat *old_stat, const struct stat *new_stat)
+{
+	return old_stat->st_size != new_stat->st_size
+		|| old_stat->st_mtime != new_stat->st_mtime
+#if defined(HAVE_STRUCT_STAT_ST_MTIM)
+		|| old_stat->st_mtim.tv_nsec != new_stat->st_mtim.tv_nsec
+#elif defined(HAVE_STRUCT_STAT_ST_MTIMENSEC)
+		|| old_stat->st_mtimensec != new_stat->st_mtimensec
+#elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC)
+		|| old_stat->st_mtimespec.tv_nsec != new_stat->st_mtimespec.tv_nsec
+#endif
+        ;
+}
+#endif
+
 static int has_state_changed(struct ast_sip_transport_state *a, struct ast_sip_transport_state *b)
 {
 	if (a->type != b->type) {
@@ -618,6 +634,12 @@ static int has_state_changed(struct ast_sip_transport_state *a, struct ast_sip_t
 		return -1;
 	}
 
+#ifdef HAVE_PJSIP_TLS_TRANSPORT_RESTART
+	if (file_stat_cmp(&a->cert_file_stat, &b->cert_file_stat) || file_stat_cmp(&a->privkey_file_stat, &b->privkey_file_stat)) {
+		return -1;
+	}
+#endif
+
 	return 0;
 }
 
@@ -659,6 +681,12 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
 		return -1;
 	}
 
+	if (transport->async_operations != 1) {
+		ast_log(LOG_WARNING, "The async_operations setting on transport '%s' has been set to '%d'. The setting can no longer be set and is always 1.\n",
+			transport_id, transport->async_operations);
+		transport->async_operations = 1;
+	}
+
 	perm_state = find_internal_state_by_transport(transport);
 	if (perm_state) {
 		ast_sorcery_diff(sorcery, perm_state->transport, transport, &changes);
@@ -736,11 +764,31 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
 		res = PJ_SUCCESS;
 	} else if (!transport->allow_reload && perm_state) {
 		/* We inherit the transport from perm state, untouched */
-		ast_log(LOG_WARNING, "Transport '%s' is not fully reloadable, not reloading: protocol, bind, TLS, TCP, ToS, or CoS options.\n", transport_id);
+#ifdef HAVE_PJSIP_TLS_TRANSPORT_RESTART
+		ast_log(LOG_NOTICE, "Transport '%s' is not fully reloadable, not reloading: protocol, bind, TLS (everything but certificate and private key if filename is unchanged), TCP, ToS, or CoS options.\n", transport_id);
+		/* If this is a TLS transport and the certificate or private key has changed, then restart the transport so it uses the new one */
+		if (transport->type == AST_TRANSPORT_TLS) {
+			if (strcmp(perm_state->transport->cert_file, temp_state->transport->cert_file) ||
+				strcmp(perm_state->transport->privkey_file, temp_state->transport->privkey_file)) {
+				ast_log(LOG_ERROR, "Unable to restart TLS transport '%s' as certificate or private key filename has changed\n",
+					transport_id);
+			} else if (file_stat_cmp(&perm_state->state->cert_file_stat, &temp_state->state->cert_file_stat) ||
+				file_stat_cmp(&perm_state->state->privkey_file_stat, &temp_state->state->privkey_file_stat)) {
+				if (pjsip_tls_transport_restart(perm_state->state->factory, &perm_state->state->host, NULL) != PJ_SUCCESS) {
+					ast_log(LOG_ERROR, "Failed to restart TLS transport '%s'\n", transport_id);
+				} else {
+					sprintf(perm_state->state->factory->info, "%s", transport_id);
+				}
+			}
+		}
+#else
+		ast_log(LOG_NOTICE, "Transport '%s' is not fully reloadable, not reloading: protocol, bind, TLS, TCP, ToS, or CoS options.\n", transport_id);
+#endif
 		temp_state->state->transport = perm_state->state->transport;
 		perm_state->state->transport = NULL;
 		temp_state->state->factory = perm_state->state->factory;
 		perm_state->state->factory = NULL;
+
 		res = PJ_SUCCESS;
 	} else if (transport->type == AST_TRANSPORT_UDP) {
 
@@ -833,6 +881,20 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
 				&temp_state->state->host, NULL, transport->async_operations,
 				&temp_state->state->factory);
 		}
+
+		if (res == PJ_SUCCESS) {
+			/*
+			 * PJSIP uses 100 bytes to store information, and during a restart will repopulate
+			 * the field so ensure there is sufficient space - even though we'll revert it after.
+			 */
+			temp_state->state->factory->info = pj_pool_alloc(
+				temp_state->state->factory->pool, (MAX(MAX_OBJECT_FIELD, 100) + 1));
+			/*
+			 * Store transport id on the factory instance so it can be used
+			 * later to look up the transport state.
+			 */
+			sprintf(temp_state->state->factory->info, "%s", transport_id);
+		}
 #else
 		ast_log(LOG_ERROR, "Transport: %s: PJSIP has not been compiled with TLS transport support, ensure OpenSSL development packages are installed\n",
 			ast_sorcery_object_get_id(obj));
@@ -902,18 +964,34 @@ static int transport_tls_file_handler(const struct aco_option *opt, struct ast_v
 		ast_string_field_set(transport, ca_list_file, var->value);
 	} else if (!strcasecmp(var->name, "ca_list_path")) {
 #ifdef HAVE_PJ_SSL_CERT_LOAD_FROM_FILES2
-		state->tls.ca_list_path = pj_str((char*)var->value);
+		state->tls.ca_list_path = pj_str((char *)var->value);
 		ast_string_field_set(transport, ca_list_path, var->value);
 #else
 		ast_log(LOG_WARNING, "Asterisk has been built against a version of pjproject that does not "
 				"support the 'ca_list_path' option. Please upgrade to version 2.4 or later.\n");
 #endif
 	} else if (!strcasecmp(var->name, "cert_file")) {
-		state->tls.cert_file = pj_str((char*)var->value);
+		state->tls.cert_file = pj_str((char *)var->value);
 		ast_string_field_set(transport, cert_file, var->value);
+#ifdef HAVE_PJSIP_TLS_TRANSPORT_RESTART
+		if (stat(var->value, &state->cert_file_stat)) {
+			ast_log(LOG_ERROR, "Failed to stat certificate file '%s' for transport '%s' due to '%s'\n",
+				var->value, ast_sorcery_object_get_id(obj), strerror(errno));
+			return -1;
+		}
+		ast_sorcery_object_set_has_dynamic_contents(transport);
+#endif
 	} else if (!strcasecmp(var->name, "priv_key_file")) {
-		state->tls.privkey_file = pj_str((char*)var->value);
+		state->tls.privkey_file = pj_str((char *)var->value);
 		ast_string_field_set(transport, privkey_file, var->value);
+#ifdef HAVE_PJSIP_TLS_TRANSPORT_RESTART
+		if (stat(var->value, &state->privkey_file_stat)) {
+			ast_log(LOG_ERROR, "Failed to stat private key file '%s' for transport '%s' due to '%s'\n",
+				var->value, ast_sorcery_object_get_id(obj), strerror(errno));
+			return -1;
+		}
+		ast_sorcery_object_set_has_dynamic_contents(transport);
+#endif
 	}
 
 	return 0;
@@ -1057,11 +1135,13 @@ static int transport_tls_bool_handler(const struct aco_option *opt, struct ast_v
 	}
 
 	if (!strcasecmp(var->name, "verify_server")) {
-		state->tls.verify_server = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
+		state->verify_server = ast_true(var->value);
 	} else if (!strcasecmp(var->name, "verify_client")) {
 		state->tls.verify_client = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
 	} else if (!strcasecmp(var->name, "require_client_cert")) {
 		state->tls.require_client_cert = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
+	} else if (!strcasecmp(var->name, "allow_wildcard_certs")) {
+		state->allow_wildcard_certs = ast_true(var->value);
 	} else {
 		return -1;
 	}
@@ -1078,7 +1158,7 @@ static int verify_server_to_str(const void *obj, const intptr_t *args, char **bu
 		return -1;
 	}
 
-	*buf = ast_strdup(AST_YESNO(state->tls.verify_server));
+	*buf = ast_strdup(AST_YESNO(state->verify_server));
 
 	return 0;
 }
@@ -1111,6 +1191,20 @@ static int require_client_cert_to_str(const void *obj, const intptr_t *args, cha
 	return 0;
 }
 
+static int allow_wildcard_certs_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	struct ast_sip_transport_state *state = find_state_by_transport(obj);
+
+	if (!state) {
+		return -1;
+	}
+
+	*buf = ast_strdup(AST_YESNO(state->allow_wildcard_certs));
+	ao2_ref(state, -1);
+
+	return 0;
+}
+
 /*! \brief Custom handler for TLS method setting */
 static int transport_tls_method_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
@@ -1653,6 +1747,7 @@ int ast_sip_initialize_sorcery_transport(void)
 	ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_server", "", transport_tls_bool_handler, verify_server_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_client", "", transport_tls_bool_handler, verify_client_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register_custom(sorcery, "transport", "require_client_cert", "", transport_tls_bool_handler, require_client_cert_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "allow_wildcard_certs", "", transport_tls_bool_handler, allow_wildcard_certs_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register_custom(sorcery, "transport", "method", "", transport_tls_method_handler, tls_method_to_str, NULL, 0, 0);
 #if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0
 	ast_sorcery_object_field_register_custom(sorcery, "transport", "cipher", "", transport_tls_cipher_handler, transport_tls_cipher_to_str, NULL, 0, 0);
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index 45495880ee722dc515165d4f1e5b45fd7490c594..7c5099917509e4287c15b4c879ce5400fe7f4c99 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -489,7 +489,10 @@ static int expiration_str2struct(const struct aco_option *opt, struct ast_variab
 static int expiration_struct2str(const void *obj, const intptr_t *args, char **buf)
 {
 	const struct ast_sip_contact *contact = obj;
-	return (ast_asprintf(buf, "%ld", contact->expiration_time.tv_sec) < 0) ? -1 : 0;
+	char secs[AST_TIME_T_LEN];
+
+	ast_time_t_to_string(contact->expiration_time.tv_sec, secs, sizeof(secs));
+	return (ast_asprintf(buf, "%s", secs) < 0) ? -1 : 0;
 }
 
 static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, int flags)
diff --git a/res/res_pjsip/pjsip_config.xml b/res/res_pjsip/pjsip_config.xml
index e66afb5b1277a5c48647ee2208ccb6d9fe5b5511..b8f6775e298bb6f84cde5b582d61c4ec6bbdad03 100644
--- a/res/res_pjsip/pjsip_config.xml
+++ b/res/res_pjsip/pjsip_config.xml
@@ -32,9 +32,33 @@
 					<synopsis>Allow support for RFC3262 provisional ACK tags</synopsis>
 					<description>
 						<enumlist>
-							<enum name="no" />
-							<enum name="required" />
-							<enum name="yes" />
+							<enum name="no">
+							<para>If set to <literal>no</literal>, do not support transmission of
+							reliable provisional responses. As UAS, if an incoming request contains 100rel
+							in the Required header, it is rejected with 420 Bad Extension.</para>
+							</enum>
+							<enum name="required">
+							<para>If set to <literal>required</literal>, require provisional responses to
+							be sent and received reliably. As UAS, incoming requests without 100rel
+							in the Supported header are rejected with 421 Extension Required. As UAC,
+							outgoing requests will have 100rel in the Required header.</para>
+							</enum>
+							<enum name="peer_supported">
+							<para>If set to <literal>peer_supported</literal>, send provisional responses
+							reliably if the request by the peer contained 100rel in the Supported or
+							Require header. As UAS, if an incoming request contains 100rel in the Supported
+							header, send 1xx responses reliably. If the request by the peer does not contain 100rel
+							in the Supported and Require header, send responses normally. As UAC, outgoing requests
+							will contain 100rel in the Supported header.</para>
+							</enum>
+							<enum name="yes">
+							<para>If set to <literal>yes</literal>, indicate the support of reliable provisional
+							responses and PRACK them if required by the peer. As UAS, if the incoming request
+							contains 100rel in the Supported header but not in the Required header, send 1xx
+							responses normally. If the incoming request contains 100rel in the Required header,
+							send 1xx responses reliably. As UAC add 100rel to the Supported header and PRACK 1xx
+							responses if required.</para>
+							</enum>
 						</enumlist>
 					</description>
 				</configOption>
@@ -289,6 +313,16 @@
 				<configOption name="allow_overlap" default="yes">
 					<synopsis>Enable RFC3578 overlap dialing support.</synopsis>
 				</configOption>
+				<configOption name="overlap_context">
+					<synopsis>Dialplan context to use for RFC3578 overlap dialing.</synopsis>
+					<description>
+						<para>Dialplan context to use for overlap dialing extension matching.
+						If not specified, the context configured for the endpoint will be used.
+						If specified, the extensions/patterns in the specified context will be used
+						for determining if a full number has been received from the endpoint.
+						</para>
+					</description>
+				</configOption>
 				<configOption name="aors">
 					<synopsis>AoR(s) to be used with the endpoint</synopsis>
 					<description><para>
@@ -986,7 +1020,7 @@
 					<synopsis>Username to use in From header for unsolicited MWI NOTIFYs to this endpoint.</synopsis>
 				</configOption>
 				<configOption name="from_domain">
-					<synopsis>Domain to user in From header for requests to this endpoint.</synopsis>
+					<synopsis>Domain to use in From header for requests to this endpoint.</synopsis>
 				</configOption>
 				<configOption name="dtls_verify">
 					<synopsis>Verify that the provided peer certificate is valid</synopsis>
@@ -1475,6 +1509,43 @@
 						responses.</para>
 					</description>
 				</configOption>
+				<configOption name="security_negotiation" default="no">
+					<synopsis>The kind of security agreement negotiation to use. Currently, only mediasec is supported.</synopsis>
+					<description>
+						<enumlist>
+							<enum name="no" />
+							<enum name="mediasec" />
+						</enumlist>
+					</description>
+				</configOption>
+				<configOption name="security_mechanisms">
+					<synopsis>List of security mechanisms supported.</synopsis>
+					<description><para>
+						This is a comma-delimited list of security mechanisms to use. Each security mechanism
+						must be in the form defined by RFC 3329 section 2.2.
+					</para></description>
+				</configOption>
+				<configOption name="geoloc_incoming_call_profile" default="">
+					<synopsis>Geolocation profile to apply to incoming calls</synopsis>
+					<description><para>
+						This geolocation profile will be applied to all calls received
+						by the channel driver from the remote endpoint before they're
+						forwarded to the dialplan.
+						</para>
+					</description>
+				</configOption>
+				<configOption name="geoloc_outgoing_call_profile" default="">
+					<synopsis>Geolocation profile to apply to outgoing calls</synopsis>
+					<description><para>
+						This geolocation profile will be applied to all calls received
+						by the channel driver from the dialplan before they're forwarded
+						the remote endpoint.
+						</para>
+					</description>
+				</configOption>
+				<configOption name="send_aoc" default="no">
+					<synopsis>Send Advice-of-Charge messages</synopsis>
+				</configOption>
 			</configObject>
 			<configObject name="auth">
 				<synopsis>Authentication type</synopsis>
@@ -1649,7 +1720,7 @@
 						will not suffice.</para></note>
 				</description>
 				<configOption name="async_operations" default="1">
-					<synopsis>Number of simultaneous Asynchronous Operations</synopsis>
+					<synopsis>Number of simultaneous Asynchronous Operations, can no longer be set, always set to 1</synopsis>
 				</configOption>
 				<configOption name="bind">
 					<synopsis>IP Address and optional port to bind to for this transport</synopsis>
@@ -1666,7 +1737,8 @@
 						A path to a .crt or .pem file can be provided.  However, only
 						the certificate is read from the file, not the private key.
 						The <literal>priv_key_file</literal> option must supply a
-						matching key file.
+						matching key file. The certificate file can be reloaded if
+						the filename in configuration remains unchanged.
 					</para></description>
 				</configOption>
 				<configOption name="cipher">
@@ -1730,6 +1802,11 @@
 				</configOption>
 				<configOption name="priv_key_file">
 					<synopsis>Private key file (TLS ONLY, not WSS)</synopsis>
+					<description><para>
+						A path to a key file can be provided. The private key file
+						can be reloaded if the filename in configuration remains
+						unchanged.
+					</para></description>
 				</configOption>
 				<configOption name="protocol" default="udp">
 					<synopsis>Protocol to use for SIP traffic</synopsis>
@@ -1790,6 +1867,18 @@
 						in-progress calls.</para>
 					</description>
 				</configOption>
+				<configOption name="allow_wildcard_certs" default="false">
+					<synopsis>Allow use of wildcards in certificates (TLS ONLY)</synopsis>
+					<description>
+					  <para>In combination with verify_server, when enabled allow use of wildcards,
+					  i.e. '*.' in certs for common,and subject alt names of type DNS for TLS
+					  transport types. Names must start with the wildcard. Partial wildcards, e.g.
+					  'f*.example.com' and 'foo.*.com' are not allowed. As well, names only match
+					  against a single level meaning '*.example.com' matches 'foo.example.com',
+					  but not 'foo.bar.example.com'.
+					  </para>
+					</description>
+				</configOption>
 				<configOption name="symmetric_transport" default="no">
 					<synopsis>Use the same transport for outgoing requests as incoming ones.</synopsis>
 					<description>
@@ -2375,6 +2464,26 @@
 				<configOption name="norefersub" default="yes">
 					<synopsis>Advertise support for RFC4488 REFER subscription suppression</synopsis>
 				</configOption>
+				<configOption name="allow_sending_180_after_183" default="no">
+					<synopsis>Allow 180 after 183</synopsis>
+					<description><para>
+						Allow Asterisk to send 180 Ringing to an endpoint
+						after 183 Session Progress has been send.
+						If disabled Asterisk will instead send only a
+						183 Session Progress to the endpoint.
+						(default: "no")
+						</para>
+					</description>
+				</configOption>
+				<configOption name="all_codecs_on_empty_reinvite" default="no">
+					<synopsis>If we should return all codecs on re-INVITE without SDP</synopsis>
+					<description><para>
+						On reception of a re-INVITE without SDP Asterisk will send an SDP
+						offer in the 200 OK response containing all configured codecs on the
+						endpoint, instead of simply those that have already been negotiated.
+						RFC 3261 specifies this as a SHOULD requirement.
+					</para></description>
+				</configOption>
 			</configObject>
 		</configFile>
 	</configInfo>
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index f7da42946703dd546054db4eaa0dd11a9b2b70bd..bae02465da79052509ac52ff93c04806c4f18c04 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -21,6 +21,7 @@
 #include <pjsip.h>
 #include <pjsip_ua.h>
 
+#include "asterisk/res_geolocation.h"
 #include "asterisk/res_pjsip.h"
 #include "include/res_pjsip_private.h"
 #include "asterisk/res_pjsip_cli.h"
@@ -181,9 +182,16 @@ static int prack_handler(const struct aco_option *opt, struct ast_variable *var,
 
 	if (ast_true(var->value)) {
 		endpoint->extensions.flags |= PJSIP_INV_SUPPORT_100REL;
+		endpoint->rel100 = AST_SIP_100REL_SUPPORTED;
+	} else if (!strcasecmp(var->value, "peer_supported")) {
+		endpoint->extensions.flags |= PJSIP_INV_SUPPORT_100REL;
+		endpoint->rel100 = AST_SIP_100REL_PEER_SUPPORTED;
 	} else if (!strcasecmp(var->value, "required")) {
 		endpoint->extensions.flags |= PJSIP_INV_REQUIRE_100REL;
-	} else if (!ast_false(var->value)){
+		endpoint->rel100 = AST_SIP_100REL_REQUIRED;
+	} else if (ast_false(var->value)) {
+		endpoint->rel100 = AST_SIP_100REL_UNSUPPORTED;
+	} else {
 		return -1;
 	}
 
@@ -194,10 +202,12 @@ static int prack_to_str(const void *obj, const intptr_t *args, char **buf)
 {
 	const struct ast_sip_endpoint *endpoint = obj;
 
-	if (endpoint->extensions.flags & PJSIP_INV_REQUIRE_100REL) {
-		*buf = "required";
-	} else if (endpoint->extensions.flags & PJSIP_INV_SUPPORT_100REL) {
+	if (endpoint->rel100 == AST_SIP_100REL_SUPPORTED) {
 		*buf = "yes";
+	} else if (endpoint->rel100 == AST_SIP_100REL_PEER_SUPPORTED) {
+		*buf = "peer_supported";
+	} else if (endpoint->rel100 == AST_SIP_100REL_REQUIRED) {
+		*buf = "required";
 	} else {
 		*buf = "no";
 	}
@@ -220,7 +230,7 @@ static int timers_handler(const struct aco_option *opt, struct ast_variable *var
 	} else if (!strcasecmp(var->value, "required")) {
 		endpoint->extensions.flags |= PJSIP_INV_REQUIRE_TIMER;
 	} else if (!strcasecmp(var->value, "always") || !strcasecmp(var->value, "forced")) {
-		endpoint->extensions.flags |= PJSIP_INV_ALWAYS_USE_TIMER;
+		endpoint->extensions.flags |= (PJSIP_INV_SUPPORT_TIMER | PJSIP_INV_ALWAYS_USE_TIMER);
 	} else if (!ast_false(var->value)) {
 		return -1;
 	}
@@ -246,6 +256,52 @@ static int timers_to_str(const void *obj, const intptr_t *args, char **buf)
 	return 0;
 }
 
+static int security_mechanism_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+
+	return ast_sip_security_mechanisms_to_str(&endpoint->security_mechanisms, 0, buf);
+}
+
+static int security_mechanism_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct ast_sip_endpoint *endpoint = obj;
+
+	return ast_sip_security_mechanism_vector_init(&endpoint->security_mechanisms, var->value);
+}
+
+static const char *security_negotiation_map[] = {
+	[AST_SIP_SECURITY_NEG_NONE] = "no",
+	[AST_SIP_SECURITY_NEG_MEDIASEC] = "mediasec",
+};
+
+static int security_negotiation_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_endpoint *endpoint = obj;
+	if (ARRAY_IN_BOUNDS(endpoint->security_negotiation, security_negotiation_map)) {
+		*buf = ast_strdup(security_negotiation_map[endpoint->security_negotiation]);
+	}
+	return 0;
+}
+
+int ast_sip_set_security_negotiation(enum ast_sip_security_negotiation *security_negotiation, const char *val) {
+	if (!strcasecmp("no", val)) {
+		*security_negotiation = AST_SIP_SECURITY_NEG_NONE;
+	} else if (!strcasecmp("mediasec", val)) {
+		*security_negotiation = AST_SIP_SECURITY_NEG_MEDIASEC;
+	} else {
+		return -1;
+	}
+	return 0;
+}
+
+static int security_negotiation_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct ast_sip_endpoint *endpoint = obj;
+
+	return ast_sip_set_security_negotiation(&endpoint->security_negotiation, var->value);
+}
+
 void ast_sip_auth_vector_destroy(struct ast_sip_auth_vector *auths)
 {
 	int i;
@@ -1539,6 +1595,36 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o
 		}
 	}
 
+	if (!ast_strlen_zero(endpoint->geoloc_incoming_call_profile) ||
+		!ast_strlen_zero(endpoint->geoloc_outgoing_call_profile)) {
+
+		if (!ast_geoloc_is_loaded()) {
+			ast_log(LOG_ERROR, "A geoloc incoming and/or outgoing call_profile was specified on endpoint '%s'"
+				" but res_geolocation is not loaded.\n", ast_sorcery_object_get_id(endpoint));
+			return -1;
+		}
+
+		if (!ast_strlen_zero(endpoint->geoloc_incoming_call_profile)) {
+			struct ast_geoloc_profile *profile = ast_geoloc_get_profile(endpoint->geoloc_incoming_call_profile);
+			if (!profile) {
+				ast_log(LOG_ERROR, "geoloc_incoming_call_profile '%s' on endpoint '%s' doesn't exist\n",
+					endpoint->geoloc_incoming_call_profile, ast_sorcery_object_get_id(endpoint));
+				return -1;
+			}
+			ao2_cleanup(profile);
+		}
+
+		if (!ast_strlen_zero(endpoint->geoloc_outgoing_call_profile)) {
+			struct ast_geoloc_profile *profile = ast_geoloc_get_profile(endpoint->geoloc_outgoing_call_profile);
+			if (!profile) {
+				ast_log(LOG_ERROR, "geoloc_outgoing_call_profile '%s' on endpoint '%s' doesn't exist\n",
+					endpoint->geoloc_outgoing_call_profile, ast_sorcery_object_get_id(endpoint));
+				return -1;
+			}
+			ao2_cleanup(profile);
+		}
+	}
+
 	return 0;
 }
 
@@ -2215,6 +2301,7 @@ int ast_res_pjsip_initialize_configuration(void)
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "asymmetric_rtp_codec", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, asymmetric_rtp_codec));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtcp_mux", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtcp_mux));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_overlap", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allow_overlap));
+	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "overlap_context", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, overlap_context));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "refer_blind_progress", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, refer_blind_progress));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "notify_early_inuse_ringing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, notify_early_inuse_ringing));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "max_audio_streams", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.max_audio_streams));
@@ -2249,6 +2336,11 @@ int ast_res_pjsip_initialize_configuration(void)
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_unauthenticated_options", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allow_unauthenticated_options));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mediasec", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, mediasec));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "call_waiting_enabled", "false", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, call_waiting_enabled));
+	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "geoloc_incoming_call_profile", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, geoloc_incoming_call_profile));
+	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "geoloc_outgoing_call_profile", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, geoloc_outgoing_call_profile));
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "security_mechanisms", "", security_mechanism_handler, security_mechanism_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "security_negotiation", "no", security_negotiation_handler, security_negotiation_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_aoc", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, send_aoc));
 
 	if (ast_sip_initialize_sorcery_transport()) {
 		ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");
@@ -2302,8 +2394,6 @@ int ast_res_pjsip_initialize_configuration(void)
 
 	load_all_endpoints();
 
-	ast_sip_location_prune_boot_contacts();
-
 	acl_change_sub = stasis_subscribe(ast_security_topic(), acl_change_stasis_cb, NULL);
 	stasis_subscription_accept_message_type(acl_change_sub, ast_named_acl_change_type());
 	stasis_subscription_set_filter(acl_change_sub, STASIS_SUBSCRIPTION_FILTER_SELECTIVE);
@@ -2409,6 +2499,16 @@ void *ast_sip_endpoint_alloc(const char *name)
 		return NULL;
 	}
 
+	if (ast_string_field_init_extended(endpoint, geoloc_incoming_call_profile) ||
+		ast_string_field_init_extended(endpoint, geoloc_outgoing_call_profile)) {
+		ao2_cleanup(endpoint);
+		return NULL;
+	}
+	if (ast_string_field_init_extended(endpoint, overlap_context)) {
+		ao2_cleanup(endpoint);
+		return NULL;
+	}
+
 	if (!(endpoint->media.codecs = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
 		ao2_cleanup(endpoint);
 		return NULL;
diff --git a/res/res_pjsip/pjsip_distributor.c b/res/res_pjsip/pjsip_distributor.c
index ea8fb021244b038f18a73354ac86c745787e861f..092e012a773b102bf4945f6ba2b6e6964e11b8fc 100644
--- a/res/res_pjsip/pjsip_distributor.c
+++ b/res/res_pjsip/pjsip_distributor.c
@@ -763,9 +763,8 @@ static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata)
 		char name[AST_UUID_STR_LEN] = "";
 		pjsip_uri *from = rdata->msg_info.from->uri;
 
-		if (PJSIP_URI_SCHEME_IS_SIP(from) || PJSIP_URI_SCHEME_IS_SIPS(from)) {
-			pjsip_sip_uri *sip_from = pjsip_uri_get_uri(from);
-			ast_copy_pj_str(name, &sip_from->user, sizeof(name));
+		if (ast_sip_is_allowed_uri(from)) {
+			ast_copy_pj_str(name, ast_sip_pjsip_uri_get_username(from), sizeof(name));
 		}
 
 		unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY);
@@ -833,6 +832,7 @@ static int extract_contact_addr(pjsip_contact_hdr *contact, struct ast_sockaddr
 		*addrs = NULL;
 		return 0;
 	}
+
 	if (!PJSIP_URI_SCHEME_IS_SIP(contact->uri) && !PJSIP_URI_SCHEME_IS_SIPS(contact->uri)) {
 		*addrs = NULL;
 		return 0;
diff --git a/res/res_pjsip/pjsip_manager.xml b/res/res_pjsip/pjsip_manager.xml
index 810a5e3ec455d3a34fe2bc4e1aba2900ea0cff42..475da8b7431aefba922ddcae4cc2067365813905 100644
--- a/res/res_pjsip/pjsip_manager.xml
+++ b/res/res_pjsip/pjsip_manager.xml
@@ -507,6 +507,9 @@
 				<parameter name="Allowoverlap">
 					<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='allow_overlap']/synopsis/node())"/></para>
 				</parameter>
+				<parameter name="OverlapContext">
+					<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='overlap_context']/synopsis/node())"/></para>
+				</parameter>
 			</syntax>
 		</managerEventInstance>
 	</managerEvent>
diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c
index 34167aef54ddbb594b39ce524bba64e1e43f18f5..de429df35a052ebc1dabf253d4bf4307a243fd1e 100644
--- a/res/res_pjsip/pjsip_options.c
+++ b/res/res_pjsip/pjsip_options.c
@@ -271,7 +271,6 @@ static pj_bool_t options_on_rx_request(pjsip_rx_data *rdata)
 {
 	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
 	pjsip_uri *ruri;
-	pjsip_sip_uri *sip_ruri;
 	char exten[AST_MAX_EXTENSION];
 
 	if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_options_method)) {
@@ -283,13 +282,12 @@ static pj_bool_t options_on_rx_request(pjsip_rx_data *rdata)
 	}
 
 	ruri = rdata->msg_info.msg->line.req.uri;
-	if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) {
+	if (!ast_sip_is_allowed_uri(ruri)) {
 		send_options_response(rdata, 416);
 		return PJ_TRUE;
 	}
 
-	sip_ruri = pjsip_uri_get_uri(ruri);
-	ast_copy_pj_str(exten, &sip_ruri->user, sizeof(exten));
+	ast_copy_pj_str(exten, ast_sip_pjsip_uri_get_username(ruri), sizeof(exten));
 
 	/*
 	 * We may want to match in the dialplan without any user
@@ -352,6 +350,8 @@ static void sip_contact_status_dtor(void *obj)
 {
 	struct ast_sip_contact_status *contact_status = obj;
 
+	ast_sip_security_mechanisms_vector_destroy(&contact_status->security_mechanisms);
+
 	ast_string_field_free_memory(contact_status);
 }
 
@@ -369,6 +369,7 @@ static struct ast_sip_contact_status *sip_contact_status_alloc(const char *name)
 		ao2_ref(contact_status, -1);
 		return NULL;
 	}
+	AST_VECTOR_INIT(&contact_status->security_mechanisms, 0);
 	strcpy(contact_status->name, name); /* SAFE */
 	return contact_status;
 }
@@ -389,6 +390,8 @@ static struct ast_sip_contact_status *sip_contact_status_copy(const struct ast_s
 	dst->rtt = src->rtt;
 	dst->status = src->status;
 	dst->last_status = src->last_status;
+
+	ast_sip_security_mechanisms_vector_copy(&dst->security_mechanisms, &src->security_mechanisms);
 	return dst;
 }
 
@@ -817,7 +820,7 @@ static void qualify_contact_cb(void *token, pjsip_event *e)
 
 	if (ast_sip_push_task(contact_callback_data->aor_options->serializer,
 		sip_options_contact_status_notify_task, contact_callback_data)) {
-		ast_log(LOG_NOTICE, "Unable to queue contact status update for '%s' on AOR '%s', state will be incorrect\n",
+		ast_log(LOG_WARNING, "Unable to queue contact status update for '%s' on AOR '%s', state will be incorrect\n",
 			ast_sorcery_object_get_id(contact_callback_data->contact),
 			contact_callback_data->aor_options->name);
 		ao2_ref(contact_callback_data, -1);
@@ -923,9 +926,9 @@ static int sip_options_qualify_contact(void *obj, void *arg, int flags)
 		ast_sip_add_header(tdata,"Proxy-Require","mediasec");
 		ast_sip_add_header(tdata,"Require","mediasec");
 
-		if(!AST_LIST_EMPTY(&endpoint->security_mechanisms)) {
+		if(!AST_LIST_EMPTY(&endpoint->secur_mechanisms)) {
 			struct security_mechanism *sec_mechanism;
-			AST_LIST_TRAVERSE(&endpoint->security_mechanisms, sec_mechanism, entry) {
+			AST_LIST_TRAVERSE(&endpoint->secur_mechanisms, sec_mechanism, entry) {
 				ast_debug(3, "Adding security header: %s\n", sec_mechanism->value);
 				ast_sip_add_header(tdata,"Security-Verify",sec_mechanism->value);
 			}
@@ -2749,6 +2752,7 @@ int ast_sip_format_contact_ami(void *obj, void *arg, int flags)
 	struct ast_sip_contact_status *status;
 	struct ast_str *buf;
 	const struct ast_sip_endpoint *endpoint = ami->arg;
+	char secs[AST_TIME_T_LEN];
 
 	buf = ast_sip_create_ami_event("ContactStatusDetail", ami);
 	if (!buf) {
@@ -2760,7 +2764,8 @@ int ast_sip_format_contact_ami(void *obj, void *arg, int flags)
 	ast_str_append(&buf, 0, "AOR: %s\r\n", wrapper->aor_id);
 	ast_str_append(&buf, 0, "URI: %s\r\n", contact->uri);
 	ast_str_append(&buf, 0, "UserAgent: %s\r\n", contact->user_agent);
-	ast_str_append(&buf, 0, "RegExpire: %ld\r\n", contact->expiration_time.tv_sec);
+	ast_time_t_to_string(contact->expiration_time.tv_sec, secs, sizeof(secs));
+	ast_str_append(&buf, 0, "RegExpire: %s\r\n", secs);
 	if (!ast_strlen_zero(contact->via_addr)) {
 		ast_str_append(&buf, 0, "ViaAddress: %s", contact->via_addr);
 		if (contact->via_port) {
diff --git a/res/res_pjsip/pjsip_transport_events.c b/res/res_pjsip/pjsip_transport_events.c
index 4df1d5e6e6229987bf568044f74fa6549da7a59e..130e26c2a7aee5f848ffce62ee03c201b035986c 100644
--- a/res/res_pjsip/pjsip_transport_events.c
+++ b/res/res_pjsip/pjsip_transport_events.c
@@ -30,6 +30,7 @@
 #include "asterisk.h"
 
 #include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_cli.h"
 #include "include/res_pjsip_private.h"
 #include "asterisk/linkedlists.h"
 #include "asterisk/vector.h"
@@ -49,8 +50,14 @@ struct transport_monitor_notifier {
 
 /*! \brief Structure for transport to be monitored */
 struct transport_monitor {
+	/*! \brief Key \<ipaddr>:\<port> */
+	char key[IP6ADDR_COLON_PORT_BUFLEN];
 	/*! \brief The underlying PJSIP transport */
 	pjsip_transport *transport;
+	/*! For debugging purposes, we save the obj_name
+	 * in case the transport goes away.
+	 */
+	char *transport_obj_name;
 	/*! Who is interested in when this transport shuts down. */
 	AST_VECTOR(, struct transport_monitor_notifier) monitors;
 };
@@ -64,12 +71,14 @@ static pjsip_tp_state_callback tpmgr_state_callback;
 /*! List of registered transport state callbacks. */
 static AST_RWLIST_HEAD(, ast_sip_tpmgr_state_callback) transport_state_list;
 
-
 /*! \brief Hashing function for struct transport_monitor */
-AO2_STRING_FIELD_HASH_FN(transport_monitor, transport->obj_name);
+AO2_STRING_FIELD_HASH_FN(transport_monitor, key);
 
 /*! \brief Comparison function for struct transport_monitor */
-AO2_STRING_FIELD_CMP_FN(transport_monitor, transport->obj_name);
+AO2_STRING_FIELD_CMP_FN(transport_monitor, key);
+
+/*! \brief Sort function for struct transport_monitor */
+AO2_STRING_FIELD_SORT_FN(transport_monitor, key);
 
 static const char *transport_state2str(pjsip_transport_state state)
 {
@@ -112,6 +121,11 @@ static void transport_monitor_dtor(void *vdoomed)
 		ao2_cleanup(notifier->data);
 	}
 	AST_VECTOR_FREE(&monitored->monitors);
+	ast_debug(3, "Transport %s(%s,%s) RefCnt: %ld : state:MONITOR_DESTROYED\n",
+		monitored->key, monitored->transport->obj_name,
+		monitored->transport->type_name,pj_atomic_get(monitored->transport->ref_cnt));
+	ast_free(monitored->transport_obj_name);
+	pjsip_transport_dec_ref(monitored->transport);
 }
 
 /*!
@@ -125,8 +139,11 @@ static void transport_monitor_dtor(void *vdoomed)
 static void transport_state_do_reg_callbacks(struct ao2_container *transports, pjsip_transport *transport)
 {
 	struct transport_monitor *monitored;
+	char key[IP6ADDR_COLON_PORT_BUFLEN];
 
-	monitored = ao2_find(transports, transport->obj_name, OBJ_SEARCH_KEY | OBJ_UNLINK);
+	AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(transport, key);
+
+	monitored = ao2_find(transports, key, OBJ_SEARCH_KEY | OBJ_UNLINK);
 	if (monitored) {
 		int idx;
 
@@ -134,14 +151,132 @@ static void transport_state_do_reg_callbacks(struct ao2_container *transports, p
 			struct transport_monitor_notifier *notifier;
 
 			notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx);
-			ast_debug(3, "running callback %p(%p) for transport %s\n",
-				notifier->cb, notifier->data, transport->obj_name);
+			ast_debug(3, "Transport %s(%s,%s) RefCnt: %ld : running callback %p(%p)\n",
+				monitored->key, monitored->transport->obj_name,
+				monitored->transport->type_name,
+				pj_atomic_get(monitored->transport->ref_cnt), notifier->cb, notifier->data);
 			notifier->cb(notifier->data);
 		}
 		ao2_ref(monitored, -1);
 	}
 }
 
+static void verify_log_result(int log_level, const pjsip_transport *transport,
+	pj_uint32_t verify_status)
+{
+	const char *status[32];
+	unsigned int count;
+	unsigned int i;
+
+	count = ARRAY_LEN(status);
+
+	if (pj_ssl_cert_get_verify_status_strings(verify_status, status, &count) != PJ_SUCCESS) {
+		ast_log(LOG_ERROR, "Error retrieving certificate verification result(s)\n");
+		return;
+	}
+
+	for (i = 0; i < count; ++i) {
+		ast_log(log_level, _A_, "Transport '%s' to remote '%.*s' - %s\n", transport->factory->info,
+			(int)pj_strlen(&transport->remote_name.host), pj_strbuf(&transport->remote_name.host),
+			status[i]);
+	}
+}
+
+static int verify_cert_name(const pj_str_t *local, const pj_str_t *remote)
+{
+	const char *p;
+	pj_ssize_t size;
+
+	ast_debug(3, "Verify certificate name: local = %.*s, remote = %.*s\n",
+		(unsigned int)pj_strlen(local), pj_strbuf(local),
+		(unsigned int)pj_strlen(remote), pj_strbuf(remote));
+
+	if (!pj_stricmp(remote, local)) {
+		return 1;
+	}
+
+	if (pj_strnicmp2(remote, "*.", 2)) {
+		return 0;
+	}
+
+	p = pj_strchr(local, '.');
+	if (!p) {
+		return 0;
+	}
+
+	size = pj_strbuf(local) + pj_strlen(local) - ++p;
+
+	return size == pj_strlen(remote) - 2 ?
+		!pj_memcmp(pj_strbuf(remote) + 2,  p, size) : 0;
+}
+
+static int verify_cert_names(const pj_str_t *host, const pj_ssl_cert_info *remote)
+{
+	unsigned int i;
+
+	for (i = 0; i < remote->subj_alt_name.cnt; ++i) {
+		/*
+		 * DNS is the only type we're matching wildcards against,
+		 * so only recheck those.
+		 */
+		if (remote->subj_alt_name.entry[i].type == PJ_SSL_CERT_NAME_DNS
+			&& verify_cert_name(host, &remote->subj_alt_name.entry[i].name)) {
+			return 1;
+		}
+	}
+
+	return verify_cert_name(host, &remote->subject.cn);
+}
+
+static int transport_tls_verify(const pjsip_transport *transport,
+	const pjsip_tls_state_info *state_info)
+{
+	pj_uint32_t verify_status;
+	const struct ast_sip_transport_state *state;
+
+	if (transport->dir == PJSIP_TP_DIR_INCOMING) {
+		return 1;
+	}
+
+	/* transport_id should always be in factory info (see config_transport) */
+	ast_assert(!ast_strlen_zero(transport->factory->info));
+
+	state = ast_sip_get_transport_state(transport->factory->info);
+	if (!state) {
+		/*
+		 * There should always be an associated state, but if for some
+		 * reason there is not then fail verification
+		 */
+		ast_log(LOG_ERROR, "Transport state not found for '%s'\n", transport->factory->info);
+		return 0;
+	}
+
+	verify_status = state_info->ssl_sock_info->verify_status;
+
+	/*
+	 * By this point pjsip has already completed its verification process. If
+	 * there was a name matching error it could be because they disallow wildcards.
+	 * If this transport has been configured to allow wildcards then we'll need
+	 * to re-check the name(s) for such.
+	 */
+	if (state->allow_wildcard_certs &&
+			(verify_status & PJ_SSL_CERT_EIDENTITY_NOT_MATCH)) {
+		if (verify_cert_names(&transport->remote_name.host,
+			state_info->ssl_sock_info->remote_cert_info)) {
+			/* A name matched a wildcard, so clear the error */
+			verify_status &= ~PJ_SSL_CERT_EIDENTITY_NOT_MATCH;
+		}
+	}
+
+	if (state->verify_server && verify_status != PJ_SSL_CERT_ESUCCESS) {
+		verify_log_result(__LOG_ERROR, transport, verify_status);
+		return 0;
+	}
+
+	verify_log_result(__LOG_NOTICE, transport, verify_status);
+	return 1;
+}
+
 /*! \brief Callback invoked when transport state changes occur */
 static void transport_state_callback(pjsip_transport *transport,
 	pjsip_transport_state state, const pjsip_transport_state_info *info)
@@ -153,20 +288,37 @@ static void transport_state_callback(pjsip_transport *transport,
 		&& (transports = ao2_global_obj_ref(active_transports))) {
 		struct transport_monitor *monitored;
 
-		ast_debug(3, "Reliable transport '%s' state:%s\n",
-			transport->obj_name, transport_state2str(state));
+		ast_debug(3, "Transport " PJSTR_PRINTF_SPEC ":%d(%s,%s): RefCnt: %ld state:%s\n",
+			PJSTR_PRINTF_VAR(transport->remote_name.host),
+			transport->remote_name.port, transport->obj_name,
+			transport->type_name,
+			pj_atomic_get(transport->ref_cnt), transport_state2str(state));
 		switch (state) {
 		case PJSIP_TP_STATE_CONNECTED:
+			if (PJSIP_TRANSPORT_IS_SECURE(transport) &&
+				!transport_tls_verify(transport, info->ext_info)) {
+				pjsip_transport_shutdown(transport);
+				return;
+			}
+
 			monitored = ao2_alloc_options(sizeof(*monitored),
 				transport_monitor_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);
 			if (!monitored) {
 				break;
 			}
 			monitored->transport = transport;
+			AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(transport, monitored->key);
+			monitored->transport_obj_name = ast_strdup(transport->obj_name);
+
 			if (AST_VECTOR_INIT(&monitored->monitors, 5)) {
 				ao2_ref(monitored, -1);
 				break;
 			}
+			pjsip_transport_add_ref(monitored->transport);
+			ast_debug(3, "Transport %s(%s,%s): RefCnt: %ld state:MONITOR_CREATED\n",
+				monitored->key,	monitored->transport_obj_name,
+				monitored->transport->type_name,
+				pj_atomic_get(monitored->transport->ref_cnt));
 
 			ao2_link(transports, monitored);
 			ao2_ref(monitored, -1);
@@ -240,8 +392,10 @@ static int transport_monitor_unregister_cb(void *obj, void *arg, int flags)
 			|| cb_data->matches(cb_data->data, notifier->data))) {
 			ao2_cleanup(notifier->data);
 			AST_VECTOR_REMOVE_UNORDERED(&monitored->monitors, idx);
-			ast_debug(3, "Unregistered monitor %p(%p) from transport %s\n",
-				notifier->cb, notifier->data, monitored->transport->obj_name);
+			ast_debug(3, "Transport %s(%s,%s) RefCnt: %ld : Unregistered monitor %p(%p)\n",
+				monitored->key, monitored->transport_obj_name,
+				monitored->transport->type_name,
+				pj_atomic_get(monitored->transport->ref_cnt), notifier->cb, notifier->data);
 		}
 	}
 	return 0;
@@ -274,11 +428,19 @@ void ast_sip_transport_monitor_unregister_all(ast_transport_monitor_shutdown_cb
 
 void ast_sip_transport_monitor_unregister(pjsip_transport *transport,
 	ast_transport_monitor_shutdown_cb cb, void *data, ast_transport_monitor_data_matcher matches)
+{
+	char key[IP6ADDR_COLON_PORT_BUFLEN];
+	AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(transport, key);
+	ast_sip_transport_monitor_unregister_key(key, cb, data, matches);
+}
+
+void ast_sip_transport_monitor_unregister_key(const char *transport_key,
+	ast_transport_monitor_shutdown_cb cb, void *data, ast_transport_monitor_data_matcher matches)
 {
 	struct ao2_container *transports;
 	struct transport_monitor *monitored;
 
-	ast_assert(transport != NULL && cb != NULL);
+	ast_assert(transport_key != NULL && cb != NULL);
 
 	transports = ao2_global_obj_ref(active_transports);
 	if (!transports) {
@@ -286,7 +448,7 @@ void ast_sip_transport_monitor_unregister(pjsip_transport *transport,
 	}
 
 	ao2_lock(transports);
-	monitored = ao2_find(transports, transport->obj_name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+	monitored = ao2_find(transports, transport_key, OBJ_SEARCH_KEY | OBJ_NOLOCK);
 	if (monitored) {
 		struct callback_data cb_data = {
 			.cb = cb,
@@ -304,17 +466,35 @@ void ast_sip_transport_monitor_unregister(pjsip_transport *transport,
 enum ast_transport_monitor_reg ast_sip_transport_monitor_register(pjsip_transport *transport,
 	ast_transport_monitor_shutdown_cb cb, void *ao2_data)
 {
-	return ast_sip_transport_monitor_register_replace(transport, cb, ao2_data, NULL);
+	char key[IP6ADDR_COLON_PORT_BUFLEN];
+	AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(transport, key);
+
+	return ast_sip_transport_monitor_register_replace_key(key, cb, ao2_data, NULL);
+}
+
+enum ast_transport_monitor_reg ast_sip_transport_monitor_register_key(const char *transport_key,
+	ast_transport_monitor_shutdown_cb cb, void *ao2_data)
+{
+	return ast_sip_transport_monitor_register_replace_key(transport_key, cb, ao2_data, NULL);
 }
 
 enum ast_transport_monitor_reg ast_sip_transport_monitor_register_replace(pjsip_transport *transport,
 	ast_transport_monitor_shutdown_cb cb, void *ao2_data, ast_transport_monitor_data_matcher matches)
+{
+	char key[IP6ADDR_COLON_PORT_BUFLEN];
+
+	AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(transport, key);
+	return ast_sip_transport_monitor_register_replace_key(key, cb, ao2_data, NULL);
+}
+
+enum ast_transport_monitor_reg ast_sip_transport_monitor_register_replace_key(const char *transport_key,
+	ast_transport_monitor_shutdown_cb cb, void *ao2_data, ast_transport_monitor_data_matcher matches)
 {
 	struct ao2_container *transports;
 	struct transport_monitor *monitored;
 	enum ast_transport_monitor_reg res = AST_TRANSPORT_MONITOR_REG_NOT_FOUND;
 
-	ast_assert(transport != NULL && cb != NULL);
+	ast_assert(transport_key != NULL && cb != NULL);
 
 	transports = ao2_global_obj_ref(active_transports);
 	if (!transports) {
@@ -322,7 +502,7 @@ enum ast_transport_monitor_reg ast_sip_transport_monitor_register_replace(pjsip_
 	}
 
 	ao2_lock(transports);
-	monitored = ao2_find(transports, transport->obj_name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+	monitored = ao2_find(transports, transport_key, OBJ_SEARCH_KEY | OBJ_NOLOCK);
 	if (monitored) {
 		struct transport_monitor_notifier new_monitor;
 		struct callback_data cb_data = {
@@ -339,12 +519,15 @@ enum ast_transport_monitor_reg ast_sip_transport_monitor_register_replace(pjsip_
 		if (AST_VECTOR_APPEND(&monitored->monitors, new_monitor)) {
 			ao2_cleanup(ao2_data);
 			res = AST_TRANSPORT_MONITOR_REG_FAILED;
-			ast_debug(3, "Register monitor %p(%p) to transport %s FAILED\n",
-				cb, ao2_data, transport->obj_name);
+			ast_debug(3, "Transport %s(%s) RefCnt: %ld : Monitor registration failed %p(%p)\n",
+				monitored->key, monitored->transport_obj_name,
+				pj_atomic_get(monitored->transport->ref_cnt), cb, ao2_data);
 		} else {
 			res = AST_TRANSPORT_MONITOR_REG_SUCCESS;
-			ast_debug(3, "Registered monitor %p(%p) to transport %s\n",
-				cb, ao2_data, transport->obj_name);
+			ast_debug(3, "Transport %s(%s,%s) RefCnt: %ld : Registered monitor %p(%p)\n",
+				monitored->key, monitored->transport_obj_name,
+				monitored->transport->type_name,
+				pj_atomic_get(monitored->transport->ref_cnt), cb, ao2_data);
 		}
 
 		ao2_ref(monitored, -1);
@@ -377,10 +560,120 @@ void ast_sip_transport_state_register(struct ast_sip_tpmgr_state_callback *eleme
 	AST_RWLIST_UNLOCK(&transport_state_list);
 }
 
+static char *cli_show_monitors(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	char *cli_rc = CLI_FAILURE;
+	int rc = 0;
+	int using_regex = 0;
+	regex_t regex = { 0, };
+	int container_count;
+	struct ao2_iterator iter;
+	struct ao2_container *sorted_monitors = NULL;
+	struct ao2_container *transports;
+	struct transport_monitor *monitored;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "pjsip show transport-monitors";
+		e->usage = "Usage: pjsip show transport-monitors [ like <pattern> ]\n"
+		            "      Show pjsip transport monitors\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 3 && a->argc != 5) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (a->argc == 5) {
+		int regrc;
+		if (strcasecmp(a->argv[3], "like")) {
+			return CLI_SHOWUSAGE;
+		}
+		regrc = regcomp(&regex, a->argv[4], REG_EXTENDED | REG_ICASE | REG_NOSUB);
+		if (regrc) {
+			char err[256];
+			regerror(regrc, &regex, err, 256);
+			ast_cli(a->fd, "PJSIP Transport Monitor: Error: %s\n", err);
+			return CLI_FAILURE;
+		}
+		using_regex = 1;
+	}
+
+	/* Get a sorted snapshot of the scheduled tasks */
+	sorted_monitors = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
+		transport_monitor_sort_fn, NULL);
+	if (!sorted_monitors) {
+		ast_cli(a->fd, "PJSIP Transport Monitor: Unable to allocate temporary container\n");
+		goto error;
+	}
+
+	transports = ao2_global_obj_ref(active_transports);
+	if (!transports) {
+		ast_cli(a->fd, "PJSIP Transport Monitor: Unable to get transports\n");
+		goto error;
+	}
+
+	ao2_lock(transports);
+	rc = ao2_container_dup(sorted_monitors, transports, 0);
+	ao2_unlock(transports);
+	ao2_ref(transports, -1);
+	if (rc != 0) {
+		ast_cli(a->fd, "PJSIP Transport Monitors: Unable to sort temporary container\n");
+		goto error;
+	}
+	container_count = ao2_container_count(sorted_monitors);
+
+	ast_cli(a->fd, "PJSIP Transport Monitors:\n\n");
+
+	ast_cli(a->fd,
+		"<Remote Host...................................> <State.....> <Direction> <RefCnt> <Monitors> <ObjName............>\n");
+
+	iter = ao2_iterator_init(sorted_monitors, AO2_ITERATOR_UNLINK);
+	for (; (monitored = ao2_iterator_next(&iter)); ao2_ref(monitored, -1)) {
+		char *state;
+
+		if (using_regex && regexec(&regex, monitored->key, 0, NULL, 0) == REG_NOMATCH) {
+			continue;
+		}
+
+		if (monitored->transport->is_destroying) {
+			state = "DESTROYING";
+		} else if (monitored->transport->is_shutdown) {
+			state = "SHUTDOWN";
+		} else {
+			state = "ACTIVE";
+		}
+
+		ast_cli(a->fd, " %-46.46s   %-10s   %-9s   %6ld   %8" PRIu64 "   %s\n",
+			monitored->key, state,
+			monitored->transport->dir == PJSIP_TP_DIR_OUTGOING ? "Outgoing" : "Incoming",
+			pj_atomic_get(monitored->transport->ref_cnt),
+			AST_VECTOR_SIZE(&monitored->monitors), monitored->transport->obj_name);
+	}
+	ao2_iterator_destroy(&iter);
+	ast_cli(a->fd, "\nTotal Transport Monitors: %d\n\n", container_count);
+	cli_rc = CLI_SUCCESS;
+error:
+	if (using_regex) {
+		regfree(&regex);
+	}
+	ao2_cleanup(sorted_monitors);
+
+	return cli_rc;
+}
+
+static struct ast_cli_entry cli_commands[] = {
+	AST_CLI_DEFINE(cli_show_monitors, "Show pjsip transport monitors"),
+};
+
 void ast_sip_destroy_transport_events(void)
 {
 	pjsip_tpmgr *tpmgr;
 
+	ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
+
 	tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint());
 	if (tpmgr) {
 		pjsip_tpmgr_set_state_cb(tpmgr, tpmgr_state_callback);
@@ -400,7 +693,7 @@ int ast_sip_initialize_transport_events(void)
 	}
 
 	transports = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
-		ACTIVE_TRANSPORTS_BUCKETS, transport_monitor_hash_fn, NULL,
+		ACTIVE_TRANSPORTS_BUCKETS, transport_monitor_hash_fn, transport_monitor_sort_fn,
 		transport_monitor_cmp_fn);
 	if (!transports) {
 		return -1;
@@ -411,5 +704,8 @@ int ast_sip_initialize_transport_events(void)
 	tpmgr_state_callback = pjsip_tpmgr_get_state_cb(tpmgr);
 	pjsip_tpmgr_set_state_cb(tpmgr, &transport_state_callback);
 
+	ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
+
+
 	return 0;
 }
diff --git a/res/res_pjsip/security_agreements.c b/res/res_pjsip/security_agreements.c
new file mode 100644
index 0000000000000000000000000000000000000000..333994dd17de6c711f8e2577624feb797b247254
--- /dev/null
+++ b/res/res_pjsip/security_agreements.c
@@ -0,0 +1,369 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Commend International
+ *
+ * Maximilian Fridrich <m.fridrich@commend.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ *
+ * \brief Interact with security agreement negotiations and mechanisms
+ *
+ * \author Maximilian Fridrich <m.fridrich@commend.com>
+ */
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+
+#include "asterisk/res_pjsip.h"
+
+static struct ast_sip_security_mechanism *ast_sip_security_mechanisms_alloc(size_t n_params)
+{
+	struct ast_sip_security_mechanism *mech;
+
+	mech = ast_calloc(1, sizeof(struct ast_sip_security_mechanism));
+	if (mech == NULL) {
+		return NULL;
+	}
+	mech->qvalue = 0.0;
+	if (AST_VECTOR_INIT(&mech->mechanism_parameters, n_params) != 0) {
+		ast_free(mech);
+		return NULL;
+	}
+
+	return mech;
+}
+
+static struct ast_sip_security_mechanism *ast_sip_security_mechanisms_copy(
+	const struct ast_sip_security_mechanism *src)
+{
+	struct ast_sip_security_mechanism *dst = NULL;
+	int i, n_params;
+	char *param;
+
+	n_params = AST_VECTOR_SIZE(&src->mechanism_parameters);
+
+	dst = ast_sip_security_mechanisms_alloc(n_params);
+	if (dst == NULL) {
+		return NULL;
+	}
+	dst->type = src->type;
+	dst->qvalue = src->qvalue;
+
+	for (i = 0; i < n_params; i++) {
+		param = ast_strdup(AST_VECTOR_GET(&src->mechanism_parameters, i));
+		AST_VECTOR_APPEND(&dst->mechanism_parameters, param);
+	}
+
+	return dst;
+}
+
+static void ast_sip_security_mechanisms_destroy(struct ast_sip_security_mechanism *mech)
+{
+	int i;
+
+	for (i = 0; i < AST_VECTOR_SIZE(&mech->mechanism_parameters); i++) {
+		ast_free(AST_VECTOR_GET(&mech->mechanism_parameters, i));
+	}
+	AST_VECTOR_FREE(&mech->mechanism_parameters);
+	ast_free(mech);
+}
+
+void ast_sip_security_mechanisms_vector_copy(struct ast_sip_security_mechanism_vector *dst,
+	const struct ast_sip_security_mechanism_vector *src)
+{
+	struct ast_sip_security_mechanism *mech;
+	int i;
+
+	ast_sip_security_mechanisms_vector_destroy(dst);
+	for (i = 0; i < AST_VECTOR_SIZE(src); i++) {
+		mech = AST_VECTOR_GET(src, i);
+		AST_VECTOR_APPEND(dst, ast_sip_security_mechanisms_copy(mech));
+	}
+};
+
+void ast_sip_security_mechanisms_vector_destroy(struct ast_sip_security_mechanism_vector *security_mechanisms)
+{
+	struct ast_sip_security_mechanism *mech;
+	int i;
+
+	if (!security_mechanisms) {
+		return;
+	}
+
+	for (i = 0; i < AST_VECTOR_SIZE(security_mechanisms); i++) {
+		mech = AST_VECTOR_GET(security_mechanisms, i);
+		ast_sip_security_mechanisms_destroy(mech);
+	}
+	AST_VECTOR_FREE(security_mechanisms);
+}
+
+static int ast_sip_str_to_security_mechanism_type(const char *security_mechanism) {
+	int result = -1;
+
+	if (!strcasecmp(security_mechanism, "msrp-tls")) {
+		result = AST_SIP_SECURITY_MECH_MSRP_TLS;
+	} else if (!strcasecmp(security_mechanism, "sdes-srtp")) {
+		result = AST_SIP_SECURITY_MECH_SDES_SRTP;
+	} else if (!strcasecmp(security_mechanism, "dtls-srtp")) {
+		result = AST_SIP_SECURITY_MECH_DTLS_SRTP;
+	}
+
+	return result;
+}
+
+static char *ast_sip_security_mechanism_type_to_str(enum ast_sip_security_mechanism_type mech_type) {
+	if (mech_type == AST_SIP_SECURITY_MECH_MSRP_TLS) {
+		return "msrp-tls";
+	} else if (mech_type == AST_SIP_SECURITY_MECH_SDES_SRTP) {
+		return "sdes-srtp";
+	} else if (mech_type == AST_SIP_SECURITY_MECH_DTLS_SRTP) {
+		return "dtls-srtp";
+	} else {
+		return NULL;
+	}
+}
+
+static int security_mechanism_to_str(const struct ast_sip_security_mechanism *security_mechanism, int add_qvalue, char **buf)
+{
+	size_t size;
+	size_t buf_size = 128;
+	int i;
+	char *ret = ast_calloc(buf_size, sizeof(char));
+
+	if (ret == NULL) {
+		return ENOMEM;
+	}
+	if (security_mechanism == NULL) {
+		ast_free(ret);
+		return EINVAL;
+	}
+
+    snprintf(ret, buf_size - 1, "%s", ast_sip_security_mechanism_type_to_str(security_mechanism->type));
+	if (add_qvalue) {
+		snprintf(ret + strlen(ret), buf_size - 1, ";q=%f.4", security_mechanism->qvalue);
+	}
+
+	size = AST_VECTOR_SIZE(&security_mechanism->mechanism_parameters);
+	for (i = 0; i < size; ++i) {
+		snprintf(ret + strlen(ret), buf_size - 1, ";%s", AST_VECTOR_GET(&security_mechanism->mechanism_parameters, i));
+	}
+
+	*buf = ret;
+	return 0;
+}
+
+int ast_sip_security_mechanisms_to_str(const struct ast_sip_security_mechanism_vector *security_mechanisms, int add_qvalue, char **buf)
+{
+	size_t vec_size;
+	struct ast_sip_security_mechanism *mech;
+	char *tmp_buf;
+	char ret[512];
+	size_t i;
+
+	if (!security_mechanisms) {
+		return -1;
+	}
+
+	vec_size = AST_VECTOR_SIZE(security_mechanisms);
+	ret[0] = '\0';
+
+	for (i = 0; i < vec_size; ++i) {
+		mech = AST_VECTOR_GET(security_mechanisms, i);
+		if (security_mechanism_to_str(mech, add_qvalue, &tmp_buf)) {
+			continue;
+		}
+		snprintf(ret + strlen(ret), sizeof(ret) - 1, "%s%s",
+		   tmp_buf, i == vec_size - 1 ? "" : ", ");
+		ast_free(tmp_buf);
+	}
+
+	*buf = ast_strdup(ret);
+
+	return 0;
+}
+
+void ast_sip_remove_headers_by_name_and_value(pjsip_msg *msg, const pj_str_t *hdr_name, const char* value)
+{
+	struct pjsip_generic_string_hdr *hdr = pjsip_msg_find_hdr_by_name(msg, hdr_name, NULL);
+	for (; hdr; hdr = pjsip_msg_find_hdr_by_name(msg, hdr_name, hdr->next)) {
+		if (value == NULL || !pj_strcmp2(&hdr->hvalue, value)) {
+			pj_list_erase(hdr);
+		}
+		if (hdr->next == hdr) {
+			break;
+		}
+	}
+}
+
+/*!
+ * \internal
+ * \brief Parses a string representing a q_value to a float.
+ *
+ * Valid q values must be in the range from 0.0 to 1.0 inclusively.
+ *
+ * \param q_value
+ * \retval The parsed qvalue or -1.0 on failure.
+ */
+static float parse_qvalue(const char *q_value) {
+	char *end;
+	float ret = strtof(q_value, &end);
+
+	if (end == q_value) {
+		/* Not a number. */
+		return -1.0;
+	} else if ('\0' != *end) {
+		/* Extra character at end of input. */
+		return -1.0;
+	} else if (ret > 1.0 || ret < 0.0) {
+		/* Out of valid range. */
+		return -1.0;
+	}
+	return ret;
+}
+
+int ast_sip_str_to_security_mechanism(struct ast_sip_security_mechanism **security_mechanism, const char *value) {
+	struct ast_sip_security_mechanism *mech;
+	char *param;
+	char *tmp;
+	char *mechanism = ast_strdupa(value);
+	int err = 0;
+	int type = -1;
+
+	mech = ast_sip_security_mechanisms_alloc(1);
+	if (!mech) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	tmp = ast_strsep(&mechanism, ';', AST_STRSEP_ALL);
+	type = ast_sip_str_to_security_mechanism_type(tmp);
+	if (type == -1) {
+		err = EINVAL;
+		goto out;
+	}
+
+	mech->type = type;
+	while ((param = ast_strsep(&mechanism, ';', AST_STRSEP_ALL))) {
+		if (!param) {
+			err = EINVAL;
+			goto out;
+		}
+		if (!strncmp(param, "q=", 2)) {
+			mech->qvalue = parse_qvalue(&param[2]);
+			if (mech->qvalue < 0.0) {
+				err = EINVAL;
+				goto out;
+			}
+			continue;
+		}
+		param = ast_strdup(param);
+		AST_VECTOR_APPEND(&mech->mechanism_parameters, param);
+	}
+
+	*security_mechanism = mech;
+
+out:
+	if (err && (mech != NULL)) {
+		ast_sip_security_mechanisms_destroy(mech);
+	}
+	return err;
+}
+
+int ast_sip_add_security_headers(struct ast_sip_security_mechanism_vector *security_mechanisms,
+		const char *header_name, int add_qval, pjsip_tx_data *tdata) {
+	struct ast_sip_security_mechanism *mech;
+	char *buf;
+	int mech_cnt;
+	int i;
+	int add_qvalue = 1;
+
+	if (!security_mechanisms || !tdata) {
+		return EINVAL;
+	}
+
+	if (!strcmp(header_name, "Security-Client")) {
+		add_qvalue = 0;
+	} else if (strcmp(header_name, "Security-Server") &&
+			strcmp(header_name, "Security-Verify")) {
+		return EINVAL;
+	}
+	/* If we're adding Security-Client headers, don't add q-value
+	 * even if the function caller requested it. */
+	add_qvalue = add_qvalue && add_qval;
+
+	mech_cnt = AST_VECTOR_SIZE(security_mechanisms);
+	for (i = 0; i < mech_cnt; ++i) {
+		mech = AST_VECTOR_GET(security_mechanisms, i);
+		if (security_mechanism_to_str(mech, add_qvalue, &buf)) {
+			continue;
+		}
+		ast_sip_add_header(tdata, header_name, buf);
+		ast_free(buf);
+	}
+	return 0;
+}
+
+void ast_sip_header_to_security_mechanism(const pjsip_generic_string_hdr *hdr,
+		struct ast_sip_security_mechanism_vector *security_mechanisms) {
+
+	struct ast_sip_security_mechanism *mech;
+	char buf[512];
+	char *hdr_val;
+	char *mechanism;
+
+	if (!security_mechanisms || !hdr) {
+		return;
+	}
+
+	if (pj_stricmp2(&hdr->name, "Security-Client") && pj_stricmp2(&hdr->name, "Security-Server") &&
+			pj_stricmp2(&hdr->name, "Security-Verify")) {
+		return;
+	}
+
+	ast_copy_pj_str(buf, &hdr->hvalue, sizeof(buf));
+	hdr_val = ast_skip_blanks(buf);
+
+	while ((mechanism = ast_strsep(&hdr_val, ',', AST_STRSEP_ALL))) {
+		if (!ast_sip_str_to_security_mechanism(&mech, mechanism)) {
+			AST_VECTOR_APPEND(security_mechanisms, mech);
+		}
+	}
+}
+
+int ast_sip_security_mechanism_vector_init(struct ast_sip_security_mechanism_vector *security_mechanisms, const char *value)
+{
+	char *val = value ? ast_strdupa(value) : NULL;
+	struct ast_sip_security_mechanism *mech;
+	char *mechanism;
+
+	ast_sip_security_mechanisms_vector_destroy(security_mechanisms);
+	if (AST_VECTOR_INIT(security_mechanisms, 1)) {
+		return -1;
+	}
+
+	if (!val) {
+		return 0;
+	}
+
+	while ((mechanism = ast_strsep(&val, ',', AST_STRSEP_ALL))) {
+		if (!ast_sip_str_to_security_mechanism(&mech, mechanism)) {
+			AST_VECTOR_APPEND(security_mechanisms, mech);
+		}
+	}
+
+	return 0;
+}
diff --git a/res/res_pjsip_aoc.c b/res/res_pjsip_aoc.c
new file mode 100644
index 0000000000000000000000000000000000000000..6e3d81dec67a7d1af07bd1b8532b21db926cb8b2
--- /dev/null
+++ b/res/res_pjsip_aoc.c
@@ -0,0 +1,698 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Michael Kuron
+ *
+ * Michael Kuron <m.kuron@gmx.de>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_pjsip</depend>
+	<support_level>extended</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjlib.h>
+
+#include "asterisk/aoc.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_session.h"
+
+static pj_xml_attr *aoc_xml_create_attr(pj_pool_t *pool, pj_xml_node *node,
+	const char *name, const char *value)
+{
+	pj_xml_attr *attr;
+
+	attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr);
+
+	pj_strdup2(pool, &attr->name, name);
+	pj_strdup2(pool, &attr->value, value);
+
+	pj_xml_add_attr(node, attr);
+	return attr;
+}
+
+static pj_xml_node *aoc_xml_create_node(pj_pool_t *pool, pj_xml_node *parent,
+	const char *name)
+{
+	pj_xml_node *node;
+
+	node = PJ_POOL_ZALLOC_T(pool, pj_xml_node);
+
+	pj_list_init(&node->attr_head);
+	pj_list_init(&node->node_head);
+
+	pj_strdup2(pool, &node->name, name);
+
+	if (parent) {
+		pj_xml_add_node(parent, node);
+	}
+
+	return node;
+}
+
+static void aoc_xml_set_node_content(pj_pool_t *pool, pj_xml_node *node,
+	const char *content)
+{
+	pj_strdup2(pool, &node->content, content);
+}
+
+static char * aoc_format_amount(pj_pool_t *pool, unsigned int amount,
+		enum ast_aoc_currency_multiplier multiplier)
+{
+	const size_t amount_max_size = 16;
+	char *amount_str;
+
+	amount_str = pj_pool_alloc(pool, amount_max_size);
+
+	switch (multiplier) {
+	case AST_AOC_MULT_ONETHOUSANDTH:
+		pj_ansi_snprintf(amount_str, amount_max_size, "%.3f", amount*0.001f);
+		break;
+	case AST_AOC_MULT_ONEHUNDREDTH:
+		pj_ansi_snprintf(amount_str, amount_max_size, "%.2f", amount*0.01f);
+		break;
+	case AST_AOC_MULT_ONETENTH:
+		pj_ansi_snprintf(amount_str, amount_max_size, "%.1f", amount*0.1f);
+		break;
+	case AST_AOC_MULT_ONE:
+		pj_ansi_snprintf(amount_str, amount_max_size, "%d", amount);
+		break;
+	case AST_AOC_MULT_TEN:
+		pj_ansi_snprintf(amount_str, amount_max_size, "%d", amount*10);
+		break;
+	case AST_AOC_MULT_HUNDRED:
+		pj_ansi_snprintf(amount_str, amount_max_size, "%d", amount*100);
+		break;
+	case AST_AOC_MULT_THOUSAND:
+		pj_ansi_snprintf(amount_str, amount_max_size, "%d", amount*1000);
+		break;
+	default:
+		pj_ansi_snprintf(amount_str, amount_max_size, "%d", amount);
+	}
+
+	return amount_str;
+}
+
+static const char *aoc_time_scale_str(enum ast_aoc_time_scale value)
+{
+	const char *str;
+
+	switch (value) {
+	default:
+	case AST_AOC_TIME_SCALE_HUNDREDTH_SECOND:
+		str = "one-hundredth-second";
+		break;
+	case AST_AOC_TIME_SCALE_TENTH_SECOND:
+		str = "one-tenth-second";
+		break;
+	case AST_AOC_TIME_SCALE_SECOND:
+		str = "one-second";
+		break;
+	case AST_AOC_TIME_SCALE_TEN_SECOND:
+		str = "ten-seconds";
+		break;
+	case AST_AOC_TIME_SCALE_MINUTE:
+		str = "one-minute";
+		break;
+	case AST_AOC_TIME_SCALE_HOUR:
+		str = "one-hour";
+		break;
+	case AST_AOC_TIME_SCALE_DAY:
+		str = "twenty-four-hours";
+		break;
+	}
+	return str;
+}
+
+static void aoc_datastore_destroy(void *obj)
+{
+	char *xml = obj;
+	ast_free(xml);
+}
+
+static const struct ast_datastore_info aoc_s_datastore = {
+	.type = "AOC-S",
+	.destroy = aoc_datastore_destroy,
+};
+
+static const struct ast_datastore_info aoc_d_datastore = {
+	.type = "AOC-D",
+	.destroy = aoc_datastore_destroy,
+};
+
+static const struct ast_datastore_info aoc_e_datastore = {
+	.type = "AOC-E",
+	.destroy = aoc_datastore_destroy,
+};
+
+struct aoc_data {
+	struct ast_sip_session *session;
+	struct ast_aoc_decoded *decoded;
+	enum ast_channel_state channel_state;
+};
+
+static void aoc_release_pool(void * data)
+{
+	pj_pool_t *pool = data;
+	pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+}
+
+static int aoc_send_as_xml(void * data)
+{
+	RAII_VAR(struct aoc_data *, adata, data, ao2_cleanup);
+	RAII_VAR(pj_pool_t *, pool, NULL, aoc_release_pool);
+
+	pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "AOC", 2048, 512);
+
+	if (!pool) {
+		ast_log(LOG_ERROR, "Could not create a memory pool for AOC XML\n");
+		return 1;
+	}
+
+	if (ast_aoc_get_msg_type(adata->decoded) == AST_AOC_D ||
+			ast_aoc_get_msg_type(adata->decoded) == AST_AOC_E) {
+		pj_xml_node *aoc;
+		pj_xml_node *aoc_type;
+		pj_xml_node *charging_info = NULL;
+		pj_xml_node *charges;
+		pj_xml_node *charge;
+		char *xml;
+		size_t size;
+		const size_t xml_max_size = 512;
+
+		aoc = aoc_xml_create_node(pool, NULL, "aoc");
+		aoc_xml_create_attr(pool, aoc, "xmlns",
+				"http://uri.etsi.org/ngn/params/xml/simservs/aoc");
+		aoc_type = aoc_xml_create_node(pool, aoc,
+				ast_aoc_get_msg_type(adata->decoded) == AST_AOC_D ? "aoc-d" : "aoc-e");
+		if (ast_aoc_get_msg_type(adata->decoded) == AST_AOC_D) {
+			charging_info = aoc_xml_create_node(pool, aoc_type, "charging-info");
+			aoc_xml_set_node_content(pool, charging_info,
+					ast_aoc_get_total_type(adata->decoded) == AST_AOC_SUBTOTAL ? "subtotal" : "total");
+		}
+		charges = aoc_xml_create_node(pool, aoc_type, "recorded-charges");
+
+		if (ast_aoc_get_charge_type(adata->decoded) == AST_AOC_CHARGE_FREE) {
+			charge = aoc_xml_create_node(pool, charges, "free-charge");
+		} else if (ast_aoc_get_charge_type(adata->decoded) == AST_AOC_CHARGE_CURRENCY ||
+				ast_aoc_get_charge_type(adata->decoded) == AST_AOC_CHARGE_UNIT) {
+			charge = aoc_xml_create_node(pool, charges, "recorded-currency-units");
+		} else {
+			charge = aoc_xml_create_node(pool, charges, "not-available");
+		}
+
+		if (ast_aoc_get_charge_type(adata->decoded) == AST_AOC_CHARGE_CURRENCY) {
+			const char *currency;
+			pj_xml_node *amount;
+			char *amount_str;
+
+			currency = ast_aoc_get_currency_name(adata->decoded);
+			if (!ast_strlen_zero(currency)) {
+				pj_xml_node *currency_id;
+
+				currency_id = aoc_xml_create_node(pool, charge, "currency-id");
+				aoc_xml_set_node_content(pool, currency_id, currency);
+			}
+
+			amount = aoc_xml_create_node(pool, charge, "currency-amount");
+			amount_str = aoc_format_amount(pool, ast_aoc_get_currency_amount(adata->decoded),
+					ast_aoc_get_currency_multiplier(adata->decoded));
+			aoc_xml_set_node_content(pool, amount, amount_str);
+		} else if (ast_aoc_get_charge_type(adata->decoded) == AST_AOC_CHARGE_UNIT) {
+			pj_xml_node *currency_id;
+			const struct ast_aoc_unit_entry *unit_entry;
+
+			currency_id = aoc_xml_create_node(pool, charge, "currency-id");
+			aoc_xml_set_node_content(pool, currency_id, "UNIT");
+
+			unit_entry = ast_aoc_get_unit_info(adata->decoded, 0);
+			if (unit_entry) {
+				pj_xml_node *amount;
+				char *amount_str;
+
+				amount = aoc_xml_create_node(pool, charge, "currency-amount");
+				amount_str = aoc_format_amount(pool, unit_entry->amount,
+						AST_AOC_MULT_ONE);
+				aoc_xml_set_node_content(pool, amount, amount_str);
+			}
+		}
+
+		xml = pj_pool_alloc(pool, xml_max_size);
+		size = pj_xml_print(aoc, xml, xml_max_size - 1, PJ_TRUE);
+		if (size >= xml_max_size) {
+			ast_log(LOG_ERROR, "aoc+xml body text too large\n");
+			return 1;
+		}
+		xml[size] = 0;
+
+		if (ast_aoc_get_msg_type(adata->decoded) == AST_AOC_D) {
+			RAII_VAR(struct ast_datastore *, datastore,
+					ast_sip_session_get_datastore(adata->session, aoc_d_datastore.type),
+					ao2_cleanup);
+			struct pjsip_tx_data *tdata;
+			struct ast_sip_body body = {
+				.type = "application",
+				.subtype = "vnd.etsi.aoc+xml",
+				.body_text = xml
+			};
+
+			if (ast_sip_create_request("INFO", adata->session->inv_session->dlg,
+					adata->session->endpoint, NULL, NULL, &tdata)) {
+				ast_log(LOG_ERROR, "Could not create AOC INFO request\n");
+				return 1;
+			}
+			if (ast_sip_add_body(tdata, &body)) {
+				ast_log(LOG_ERROR, "Could not add body to AOC INFO request\n");
+				pjsip_tx_data_dec_ref(tdata);
+				return 1;
+			}
+			ast_sip_session_send_request(adata->session, tdata);
+
+			if (!datastore) {
+				datastore = ast_sip_session_alloc_datastore(&aoc_d_datastore, aoc_d_datastore.type);
+				if (!datastore) {
+					ast_log(LOG_ERROR, "Unable to create datastore for AOC-D.\n");
+					return 1;
+				}
+				datastore->data = NULL;
+				if (ast_sip_session_add_datastore(adata->session, datastore)) {
+					ast_log(LOG_ERROR, "Unable to create datastore for AOC-D.\n");
+					return 1;
+				}
+			} else {
+				ast_free(datastore->data);
+			}
+
+			aoc_xml_set_node_content(pool, charging_info, "total");
+			size = pj_xml_print(aoc, xml, xml_max_size - 1, PJ_TRUE);
+			xml[size] = 0;
+			datastore->data = ast_strdup(xml);
+		} else if (ast_aoc_get_msg_type(adata->decoded) == AST_AOC_E) {
+			RAII_VAR(struct ast_datastore *, datastore,
+					ast_sip_session_get_datastore(adata->session, aoc_e_datastore.type),
+					ao2_cleanup);
+			if (!datastore) {
+				datastore = ast_sip_session_alloc_datastore(&aoc_e_datastore, aoc_e_datastore.type);
+				if (!datastore) {
+					ast_log(LOG_ERROR, "Unable to create datastore for AOC-E.\n");
+					return 1;
+				}
+				datastore->data = NULL;
+				if (ast_sip_session_add_datastore(adata->session, datastore)) {
+					ast_log(LOG_ERROR, "Unable to create datastore for AOC-E.\n");
+					return 1;
+				}
+			} else {
+				ast_free(datastore->data);
+			}
+			datastore->data = ast_strdup(xml);
+		}
+	} else if (ast_aoc_get_msg_type(adata->decoded) == AST_AOC_S) {
+		pj_xml_node *aoc;
+		pj_xml_node *aoc_type;
+		pj_xml_node *charged_items;
+		const struct ast_aoc_s_entry *entry;
+		int idx;
+		char *xml;
+		size_t size;
+		const size_t xml_max_size = 1024;
+
+		aoc = aoc_xml_create_node(pool, NULL, "aoc");
+		aoc_xml_create_attr(pool, aoc, "xmlns",
+				"http://uri.etsi.org/ngn/params/xml/simservs/aoc");
+		aoc_type = aoc_xml_create_node(pool, aoc, "aoc-s");
+		charged_items = aoc_xml_create_node(pool, aoc_type, "charged-items");
+
+		for (idx = 0; idx < ast_aoc_s_get_count(adata->decoded); idx++) {
+			pj_xml_node *charged_item;
+			pj_xml_node *charge;
+
+			if (!(entry = ast_aoc_s_get_rate_info(adata->decoded, idx))) {
+				break;
+			}
+
+			if (entry->charged_item == AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION) {
+				charged_item = aoc_xml_create_node(pool, charged_items, "basic");
+			} else if (entry->charged_item == AST_AOC_CHARGED_ITEM_CALL_ATTEMPT) {
+				charged_item = aoc_xml_create_node(pool, charged_items,
+						"communication-attempt");
+			} else if (entry->charged_item == AST_AOC_CHARGED_ITEM_CALL_SETUP) {
+				charged_item = aoc_xml_create_node(pool, charged_items,
+						"communication-setup");
+			} else {
+				continue;
+			}
+
+			if (entry->rate_type == AST_AOC_RATE_TYPE_FREE) {
+				charge = aoc_xml_create_node(pool, charged_item, "free-charge");
+			} else if (entry->rate_type == AST_AOC_RATE_TYPE_FLAT) {
+				charge = aoc_xml_create_node(pool, charged_item, "flat-rate");
+			} else if (entry->rate_type == AST_AOC_RATE_TYPE_DURATION &&
+					entry->charged_item == AST_AOC_CHARGED_ITEM_BASIC_COMMUNICATION) {
+				charge = aoc_xml_create_node(pool, charged_item, "price-time");
+			} else {
+				continue;
+			}
+
+			if (entry->rate_type == AST_AOC_RATE_TYPE_DURATION ||
+					entry->rate_type == AST_AOC_RATE_TYPE_FLAT) {
+				const char *currency;
+				pj_xml_node *amount;
+				uint32_t amount_val;
+				enum ast_aoc_currency_multiplier multiplier_val;
+				char *amount_str;
+
+				currency = (entry->rate_type == AST_AOC_RATE_TYPE_DURATION ?
+						entry->rate.duration.currency_name :
+						entry->rate.flat.currency_name);
+				if (!ast_strlen_zero(currency)) {
+					pj_xml_node *currency_id;
+
+					currency_id = aoc_xml_create_node(pool, charge, "currency-id");
+					aoc_xml_set_node_content(pool, currency_id, currency);
+				}
+
+				amount = aoc_xml_create_node(pool, charge, "currency-amount");
+				amount_val = (entry->rate_type == AST_AOC_RATE_TYPE_DURATION ?
+						entry->rate.duration.amount : entry->rate.flat.amount);
+				multiplier_val = (entry->rate_type == AST_AOC_RATE_TYPE_DURATION ?
+						entry->rate.duration.multiplier : entry->rate.flat.multiplier);
+				amount_str = aoc_format_amount(pool, amount_val, multiplier_val);
+				aoc_xml_set_node_content(pool, amount, amount_str);
+			}
+
+			if (entry->rate_type == AST_AOC_RATE_TYPE_DURATION) {
+				pj_xml_node *length_time_unit;
+				pj_xml_node *time_unit;
+				char *time_str;
+				pj_xml_node *scale;
+				pj_xml_node *charging_type;
+
+				length_time_unit = aoc_xml_create_node(pool, charge, "length-time-unit");
+				time_unit = aoc_xml_create_node(pool, length_time_unit, "time-unit");
+				time_str = aoc_format_amount(pool, entry->rate.duration.time,
+						AST_AOC_MULT_ONE);
+				aoc_xml_set_node_content(pool, time_unit, time_str);
+				scale = aoc_xml_create_node(pool, length_time_unit, "scale");
+				aoc_xml_set_node_content(pool, scale,
+						aoc_time_scale_str(entry->rate.duration.time_scale));
+				charging_type = aoc_xml_create_node(pool, charge, "charging-type");
+				aoc_xml_set_node_content(pool, charging_type,
+						entry->rate.duration.charging_type ? "step-function" :
+						"continuous");
+			}
+		}
+
+		xml = pj_pool_alloc(pool, xml_max_size);
+		size = pj_xml_print(aoc, xml, xml_max_size - 1, PJ_TRUE);
+		if (size >= xml_max_size) {
+			ast_log(LOG_ERROR, "aoc+xml body text too large\n");
+			return 1;
+		}
+		xml[size] = 0;
+
+		if (adata->channel_state == AST_STATE_UP ||
+				adata->session->call_direction == AST_SIP_SESSION_OUTGOING_CALL) {
+			struct pjsip_tx_data *tdata;
+			struct ast_sip_body body = {
+				.type = "application",
+				.subtype = "vnd.etsi.aoc+xml",
+				.body_text = xml
+			};
+
+			if (ast_sip_create_request("INFO", adata->session->inv_session->dlg,
+					adata->session->endpoint, NULL, NULL, &tdata)) {
+				ast_log(LOG_ERROR, "Could not create AOC INFO request\n");
+				return 1;
+			}
+			if (ast_sip_add_body(tdata, &body)) {
+				ast_log(LOG_ERROR, "Could not add body to AOC INFO request\n");
+				pjsip_tx_data_dec_ref(tdata);
+				return 1;
+			}
+			ast_sip_session_send_request(adata->session, tdata);
+		} else {
+			RAII_VAR(struct ast_datastore *, datastore,
+					ast_sip_session_get_datastore(adata->session, aoc_s_datastore.type),
+					ao2_cleanup);
+			if (!datastore) {
+				datastore = ast_sip_session_alloc_datastore(&aoc_s_datastore, aoc_s_datastore.type);
+				if (!datastore) {
+					ast_log(LOG_ERROR, "Unable to create datastore for AOC-S.\n");
+					return 1;
+				}
+				if (ast_sip_session_add_datastore(adata->session, datastore)) {
+					ast_log(LOG_ERROR, "Unable to create datastore for AOC-S.\n");
+					return 1;
+				}
+			} else {
+				ast_free(datastore->data);
+			}
+			datastore->data = ast_strdup(xml);
+		}
+	}
+
+	return 0;
+}
+
+static void aoc_data_destroy(void * data)
+{
+	struct aoc_data *adata = data;
+
+	ast_aoc_destroy_decoded(adata->decoded);
+	ao2_cleanup(adata->session);
+}
+
+static struct ast_frame *aoc_framehook(struct ast_channel *ast, struct ast_frame *f,
+		enum ast_framehook_event event, void *data)
+{
+	struct ast_sip_channel_pvt *channel;
+	struct aoc_data *adata;
+
+	if (!f || f->frametype != AST_FRAME_CONTROL || event != AST_FRAMEHOOK_EVENT_WRITE ||
+			f->subclass.integer != AST_CONTROL_AOC) {
+		return f;
+	}
+
+	adata = ao2_alloc(sizeof(struct aoc_data), aoc_data_destroy);
+	if (!adata) {
+		ast_log(LOG_ERROR, "Failed to allocate AOC data\n");
+		return f;
+	}
+
+	adata->decoded = ast_aoc_decode((struct ast_aoc_encoded *) f->data.ptr, f->datalen, ast);
+	if (!adata->decoded) {
+		ast_log(LOG_ERROR, "Error decoding indicated AOC data\n");
+		ao2_ref(adata, -1);
+		return f;
+	}
+
+	channel = ast_channel_tech_pvt(ast);
+	adata->session = ao2_bump(channel->session);
+	adata->channel_state = ast_channel_state(ast);
+
+	if (ast_sip_push_task(adata->session->serializer, aoc_send_as_xml, adata)) {
+		ast_log(LOG_ERROR, "Unable to send AOC XML for channel %s\n", ast_channel_name(ast));
+		ao2_ref(adata, -1);
+	}
+	return &ast_null_frame;
+}
+
+static int aoc_consume(void *data, enum ast_frame_type type)
+{
+	return (type == AST_FRAME_CONTROL) ? 1 : 0;
+}
+
+static void aoc_attach_framehook(struct ast_sip_session *session)
+{
+	int framehook_id;
+	static struct ast_framehook_interface hook = {
+		.version = AST_FRAMEHOOK_INTERFACE_VERSION,
+		.event_cb = aoc_framehook,
+		.consume_cb = aoc_consume,
+	};
+
+	if (!session->channel || !session->endpoint->send_aoc) {
+		return;
+	}
+
+	ast_channel_lock(session->channel);
+
+	framehook_id = ast_framehook_attach(session->channel, &hook);
+	if (framehook_id < 0) {
+		ast_log(LOG_WARNING, "Could not attach AOC Frame hook, AOC will be unavailable on '%s'\n",
+			ast_channel_name(session->channel));
+	}
+
+	ast_channel_unlock(session->channel);
+}
+
+static int aoc_incoming_invite_request(struct ast_sip_session *session,
+		struct pjsip_rx_data *rdata)
+{
+	aoc_attach_framehook(session);
+	return 0;
+}
+
+static void aoc_outgoing_invite_request(struct ast_sip_session *session,
+		struct pjsip_tx_data *tdata)
+{
+	aoc_attach_framehook(session);
+}
+
+static void aoc_bye_outgoing_response(struct ast_sip_session *session,
+		struct pjsip_tx_data *tdata)
+{
+	struct ast_sip_body body = {
+		.type = "application",
+		.subtype = "vnd.etsi.aoc+xml",
+	};
+	RAII_VAR(struct ast_datastore *, datastore_d, ast_sip_session_get_datastore(session,
+			aoc_d_datastore.type), ao2_cleanup);
+	RAII_VAR(struct ast_datastore *, datastore_e, ast_sip_session_get_datastore(session,
+			aoc_e_datastore.type), ao2_cleanup);
+
+	if (datastore_e) {
+		body.body_text = datastore_e->data;
+	} else if (datastore_d) {
+		body.body_text = datastore_d->data;
+	}
+	else {
+		return;
+	}
+
+	if (ast_sip_add_body(tdata, &body)) {
+		ast_log(LOG_ERROR, "Could not add body to AOC INFO request\n");
+	}
+}
+
+static void aoc_bye_outgoing_request(struct ast_sip_session *session,
+		struct pjsip_tx_data *tdata)
+{
+	struct ast_sip_body body = {
+		.type = "application",
+		.subtype = "vnd.etsi.aoc+xml",
+	};
+	RAII_VAR(struct ast_datastore *, datastore_d, ast_sip_session_get_datastore(session,
+			aoc_d_datastore.type), ao2_cleanup);
+	RAII_VAR(struct ast_datastore *, datastore_e, ast_sip_session_get_datastore(session,
+			aoc_e_datastore.type), ao2_cleanup);
+
+	if (datastore_e) {
+		body.body_text = datastore_e->data;
+	} else if (datastore_d) {
+		body.body_text = datastore_d->data;
+	}
+	else {
+		return;
+	}
+
+	if (ast_sip_add_body(tdata, &body)) {
+		ast_log(LOG_ERROR, "Could not add body to AOC INFO request\n");
+	}
+}
+
+static void aoc_invite_outgoing_response(struct ast_sip_session *session,
+		struct pjsip_tx_data *tdata)
+{
+	pjsip_msg_body *multipart_body;
+	pjsip_multipart_part *part;
+	pj_str_t body_text;
+	pj_str_t type;
+	pj_str_t subtype;
+	RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session,
+			aoc_s_datastore.type), ao2_cleanup);
+
+	if (tdata->msg->line.status.code != 180 && tdata->msg->line.status.code != 183 &&
+			tdata->msg->line.status.code != 200) {
+		return;
+	}
+
+	if (!datastore) {
+		return;
+	}
+
+	if (tdata->msg->body && pjsip_media_type_cmp(&tdata->msg->body->content_type,
+			&pjsip_media_type_multipart_mixed, 0) == 0) {
+		multipart_body = tdata->msg->body;
+	} else {
+		pjsip_sdp_info *tdata_sdp_info;
+
+		tdata_sdp_info = pjsip_tdata_get_sdp_info(tdata);
+		if (tdata_sdp_info->sdp) {
+			pj_status_t rc;
+
+			rc = pjsip_create_multipart_sdp_body(tdata->pool, tdata_sdp_info->sdp,
+					&multipart_body);
+			if (rc != PJ_SUCCESS) {
+				ast_log(LOG_ERROR, "Unable to create sdp multipart body\n");
+				return;
+			}
+		} else {
+			multipart_body = pjsip_multipart_create(tdata->pool,
+					&pjsip_media_type_multipart_mixed, NULL);
+		}
+	}
+
+	part = pjsip_multipart_create_part(tdata->pool);
+	pj_strdup2(tdata->pool, &body_text, datastore->data);
+	pj_cstr(&type, "application");
+	pj_cstr(&subtype, "vnd.etsi.aoc+xml");
+	part->body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &body_text);
+	pjsip_multipart_add_part(tdata->pool, multipart_body, part);
+
+	tdata->msg->body = multipart_body;
+}
+
+static struct ast_sip_session_supplement aoc_bye_supplement = {
+	.method = "BYE",
+	.priority = AST_SIP_SUPPLEMENT_PRIORITY_LAST,
+	.outgoing_request = aoc_bye_outgoing_request,
+	.outgoing_response = aoc_bye_outgoing_response,
+};
+
+static struct ast_sip_session_supplement aoc_invite_supplement = {
+	.method = "INVITE",
+	.priority = AST_SIP_SUPPLEMENT_PRIORITY_LAST,
+	.incoming_request = aoc_incoming_invite_request,
+	.outgoing_request = aoc_outgoing_invite_request,
+	.outgoing_response = aoc_invite_outgoing_response,
+};
+
+static int load_module(void)
+{
+	ast_sip_session_register_supplement(&aoc_bye_supplement);
+	ast_sip_session_register_supplement(&aoc_invite_supplement);
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_sip_session_unregister_supplement(&aoc_bye_supplement);
+	ast_sip_session_unregister_supplement(&aoc_invite_supplement);
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP AOC Support",
+	.support_level = AST_MODULE_SUPPORT_EXTENDED,
+	.load = load_module,
+	.unload = unload_module,
+	.load_pri = AST_MODPRI_CHANNEL_DEPEND,
+	.requires = "res_pjsip",
+);
diff --git a/res/res_pjsip_caller_id.c b/res/res_pjsip_caller_id.c
index 96e2cf96e40d6cef1d1fbf5fee2db7201e98c2f3..b11e5908bfe26fb8d30bf76ba9206f2a0dbed057 100644
--- a/res/res_pjsip_caller_id.c
+++ b/res/res_pjsip_caller_id.c
@@ -35,91 +35,6 @@
 #include "asterisk/callerid.h"
 #include "asterisk/conversions.h"
 
-/*!
- * \internal
- * \brief Set an ast_party_id name and number based on an identity header.
- * \param hdr From, P-Asserted-Identity, or Remote-Party-ID header on incoming message
- * \param[out] id The ID to set data on
- */
-static void set_id_from_hdr(pjsip_fromto_hdr *hdr, struct ast_party_id *id)
-{
-	char cid_name[AST_CHANNEL_NAME];
-	char cid_num[AST_CHANNEL_NAME];
-	pjsip_sip_uri *uri;
-	pjsip_name_addr *id_name_addr = (pjsip_name_addr *) hdr->uri;
-	char *semi;
-
-	uri = pjsip_uri_get_uri(id_name_addr);
-	ast_copy_pj_str(cid_name, &id_name_addr->display, sizeof(cid_name));
-	ast_copy_pj_str(cid_num, &uri->user, sizeof(cid_num));
-
-	/* Always truncate caller-id number at a semicolon. */
-	semi = strchr(cid_num, ';');
-	if (semi) {
-		/*
-		 * We need to be able to handle URI's looking like
-		 * "sip:1235557890;phone-context=national@x.x.x.x;user=phone"
-		 *
-		 * Where the uri->user field will result in:
-		 * "1235557890;phone-context=national"
-		 *
-		 * People don't care about anything after the semicolon
-		 * showing up on their displays even though the RFC
-		 * allows the semicolon.
-		 */
-		*semi = '\0';
-	}
-
-	ast_free(id->name.str);
-	id->name.str = ast_strdup(cid_name);
-	if (!ast_strlen_zero(cid_name)) {
-		id->name.valid = 1;
-	}
-	ast_free(id->number.str);
-	id->number.str = ast_strdup(cid_num);
-	if (!ast_strlen_zero(cid_num)) {
-		id->number.valid = 1;
-	}
-}
-
-/*!
- * \internal
- * \brief Get a P-Asserted-Identity or Remote-Party-ID header from an incoming message
- *
- * This function will parse the header as if it were a From header. This allows for us
- * to easily manipulate the URI, as well as add, modify, or remove parameters from the
- * header
- *
- * \param rdata The incoming message
- * \param header_name The name of the ID header to find
- * \retval NULL No ID header present or unable to parse ID header
- * \retval non-NULL The parsed ID header
- */
-static pjsip_fromto_hdr *get_id_header(pjsip_rx_data *rdata, const pj_str_t *header_name)
-{
-	static const pj_str_t from = { "From", 4 };
-	pj_str_t header_content;
-	pjsip_fromto_hdr *parsed_hdr;
-	pjsip_generic_string_hdr *ident = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
-			header_name, NULL);
-	int parsed_len;
-
-	if (!ident) {
-		return NULL;
-	}
-
-	pj_strdup_with_null(rdata->tp_info.pool, &header_content, &ident->hvalue);
-
-	parsed_hdr = pjsip_parse_hdr(rdata->tp_info.pool, &from, header_content.ptr,
-			pj_strlen(&header_content), &parsed_len);
-
-	if (!parsed_hdr) {
-		return NULL;
-	}
-
-	return parsed_hdr;
-}
-
 /*!
  * \internal
  * \brief Set an ANI2 integer based on OLI data in a From header
@@ -161,130 +76,6 @@ static int set_id_from_oli(pjsip_rx_data *rdata, int *ani2)
 	return ast_str_to_int(oli, ani2);
 }
 
-/*!
- * \internal
- * \brief Set an ast_party_id structure based on data in a P-Asserted-Identity header
- *
- * This makes use of \ref set_id_from_hdr for setting name and number. It uses
- * the contents of a Privacy header in order to set presentation information.
- *
- * \param rdata The incoming message
- * \param[out] id The ID to set
- * \retval 0 Successfully set the party ID
- * \retval non-zero Could not set the party ID
- */
-static int set_id_from_pai(pjsip_rx_data *rdata, struct ast_party_id *id)
-{
-	static const pj_str_t pai_str = { "P-Asserted-Identity", 19 };
-	static const pj_str_t privacy_str = { "Privacy", 7 };
-	pjsip_fromto_hdr *pai_hdr = get_id_header(rdata, &pai_str);
-	pjsip_generic_string_hdr *privacy;
-
-	if (!pai_hdr) {
-		return -1;
-	}
-
-	set_id_from_hdr(pai_hdr, id);
-
-	if (!id->number.valid) {
-		return -1;
-	}
-
-	privacy = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &privacy_str, NULL);
-	if (!privacy || !pj_stricmp2(&privacy->hvalue, "none")) {
-		id->number.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
-		id->name.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
-	} else {
-		id->number.presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED;
-		id->name.presentation = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED;
-	}
-
-	return 0;
-}
-
-/*!
- * \internal
- * \brief Set an ast_party_id structure based on data in a Remote-Party-ID header
- *
- * This makes use of \ref set_id_from_hdr for setting name and number. It uses
- * the privacy and screen parameters in order to set presentation information.
- *
- * \param rdata The incoming message
- * \param[out] id The ID to set
- * \retval 0 Succesfully set the party ID
- * \retval non-zero Could not set the party ID
- */
-static int set_id_from_rpid(pjsip_rx_data *rdata, struct ast_party_id *id)
-{
-	static const pj_str_t rpid_str = { "Remote-Party-ID", 15 };
-	static const pj_str_t privacy_str = { "privacy", 7 };
-	static const pj_str_t screen_str = { "screen", 6 };
-	pjsip_fromto_hdr *rpid_hdr = get_id_header(rdata, &rpid_str);
-	pjsip_param *screen;
-	pjsip_param *privacy;
-
-	if (!rpid_hdr) {
-		return -1;
-	}
-
-	set_id_from_hdr(rpid_hdr, id);
-
-	if (!id->number.valid) {
-		return -1;
-	}
-
-	privacy = pjsip_param_find(&rpid_hdr->other_param, &privacy_str);
-	screen = pjsip_param_find(&rpid_hdr->other_param, &screen_str);
-	if (privacy && !pj_stricmp2(&privacy->value, "full")) {
-		id->number.presentation = AST_PRES_RESTRICTED;
-		id->name.presentation = AST_PRES_RESTRICTED;
-	} else {
-		id->number.presentation = AST_PRES_ALLOWED;
-		id->name.presentation = AST_PRES_ALLOWED;
-	}
-	if (screen && !pj_stricmp2(&screen->value, "yes")) {
-		id->number.presentation |= AST_PRES_USER_NUMBER_PASSED_SCREEN;
-		id->name.presentation |= AST_PRES_USER_NUMBER_PASSED_SCREEN;
-	} else {
-		id->number.presentation |= AST_PRES_USER_NUMBER_UNSCREENED;
-		id->name.presentation |= AST_PRES_USER_NUMBER_UNSCREENED;
-	}
-
-	return 0;
-}
-
-/*!
- * \internal
- * \brief Set an ast_party_id structure based on data in a From
- *
- * This makes use of \ref set_id_from_hdr for setting name and number. It uses
- * no information from the message in order to set privacy. It relies on endpoint
- * configuration for privacy information.
- *
- * \param rdata The incoming message
- * \param[out] id The ID to set
- * \retval 0 Succesfully set the party ID
- * \retval non-zero Could not set the party ID
- */
-static int set_id_from_from(struct pjsip_rx_data *rdata, struct ast_party_id *id)
-{
-	pjsip_fromto_hdr *from = pjsip_msg_find_hdr(rdata->msg_info.msg,
-			PJSIP_H_FROM, rdata->msg_info.msg->hdr.next);
-
-	if (!from) {
-		/* This had better not happen */
-		return -1;
-	}
-
-	set_id_from_hdr(from, id);
-
-	if (!id->number.valid) {
-		return -1;
-	}
-
-	return 0;
-}
-
 /*!
  * \internal
  * \brief Determine if a connected line update should be queued
@@ -390,7 +181,7 @@ static void update_incoming_connected_line(struct ast_sip_session *session, pjsi
 	}
 
 	ast_party_id_init(&id);
-	if (!set_id_from_pai(rdata, &id) || !set_id_from_rpid(rdata, &id)) {
+	if (!ast_sip_set_id_connected_line(rdata, &id)) {
 		if (should_queue_connected_line_update(session, &id)) {
 			queue_connected_line_update(session, &id);
 		}
@@ -419,22 +210,8 @@ static int caller_id_incoming_request(struct ast_sip_session *session, pjsip_rx_
 		 * INVITE.  Set the session ID directly because the channel
 		 * has not been created yet.
 		 */
-		if (session->endpoint->id.trust_inbound
-			&& (!set_id_from_pai(rdata, &session->id)
-				|| !set_id_from_rpid(rdata, &session->id))) {
-			ast_free(session->id.tag);
-			session->id.tag = ast_strdup(session->endpoint->id.self.tag);
-			return 0;
-		}
-		ast_party_id_copy(&session->id, &session->endpoint->id.self);
-		if (!session->endpoint->id.self.number.valid) {
-			set_id_from_from(rdata, &session->id);
-		}
-		if (!set_id_from_oli(rdata, &ani2)) {
-			session->ani2 = ani2;
-		} else {
-			session->ani2 = 0;
-		}
+		ast_sip_set_id_from_invite(rdata, &session->id, &session->endpoint->id.self, session->endpoint->id.trust_inbound);
+		session->ani2 = set_id_from_oli(rdata, &ani2) ? 0 : ani2;
 	} else {
 		/*
 		 * ReINVITE or UPDATE.  Check for changes to the ID and queue
diff --git a/res/res_pjsip_config_wizard.c b/res/res_pjsip_config_wizard.c
index 4c1c59b0cade7b824ab5f075dcafdf07021a8fb4..59976b15848dc3950e858be9c07265b4b7caa06a 100644
--- a/res/res_pjsip_config_wizard.c
+++ b/res/res_pjsip_config_wizard.c
@@ -65,25 +65,24 @@
 			endpoint, aor, contact, auth and phoneprov objects necessary for a phone to
 			get phone provisioning information, register, and make and receive calls.
 			A hint is also created in the default context for extension 1000.</para>
-			<para> </para>
-
-			<para>[myphone]</para>
-			<para>type = wizard</para>
-			<para>sends_auth = no</para>
-			<para>accepts_auth = yes</para>
-			<para>sends_registrations = no</para>
-			<para>accepts_registrations = yes</para>
-			<para>has_phoneprov = yes</para>
-			<para>transport = ipv4</para>
-			<para>has_hint = yes</para>
-			<para>hint_exten = 1000</para>
-			<para>inbound_auth/username = testname</para>
-			<para>inbound_auth/password = test password</para>
-			<para>endpoint/allow = ulaw</para>
-			<para>endpoint/context = default</para>
-			<para>phoneprov/MAC = 001122aa4455</para>
-			<para>phoneprov/PROFILE = profile1</para>
-			<para> </para>
+			<example title="myphone">
+			[myphone]
+			type = wizard
+			sends_auth = no
+			accepts_auth = yes
+			sends_registrations = no
+			accepts_registrations = yes
+			has_phoneprov = yes
+			transport = ipv4
+			has_hint = yes
+			hint_exten = 1000
+			inbound_auth/username = testname
+			inbound_auth/password = test password
+			endpoint/allow = ulaw
+			endpoint/context = default
+			phoneprov/MAC = 001122aa4455
+			phoneprov/PROFILE = profile1
+			</example>
 
 			<para>The first 8 items are specific to the wizard.  The rest of the items
 			are passed verbatim to the underlying objects.</para>
@@ -92,21 +91,20 @@
 			<para>The following configuration snippet would create the
 			endpoint, aor, contact, auth, identify and registration objects necessary for a trunk
 			to another pbx or ITSP that requires registration.</para>
-			<para> </para>
-
-			<para>[mytrunk]</para>
-			<para>type = wizard</para>
-			<para>sends_auth = yes</para>
-			<para>accepts_auth = no</para>
-			<para>sends_registrations = yes</para>
-			<para>accepts_registrations = no</para>
-			<para>transport = ipv4</para>
-			<para>remote_hosts = sip1.myitsp.com:5060,sip2.myitsp.com:5060</para>
-			<para>outbound_auth/username = testname</para>
-			<para>outbound_auth/password = test password</para>
-			<para>endpoint/allow = ulaw</para>
-			<para>endpoint/context = default</para>
-			<para> </para>
+			<example title="mytrunk">
+			[mytrunk]
+			type = wizard
+			sends_auth = yes
+			accepts_auth = no
+			sends_registrations = yes
+			accepts_registrations = no
+			transport = ipv4
+			remote_hosts = sip1.myitsp.com:5060,sip2.myitsp.com:5060
+			outbound_auth/username = testname
+			outbound_auth/password = test password
+			endpoint/allow = ulaw
+			endpoint/context = default
+			</example>
 
 			<para>Of course, any of the items in either example could be placed into
 			templates and shared among wizard objects.</para>
@@ -227,10 +225,10 @@
 					<para>   <literal>exten =&gt; &lt;hint_exten&gt;,1,&lt;hint_application&gt;</literal></para>
 					<para> </para>
 					<para>You can specify any valid extensions.conf application expression.</para>
-					<para>Examples: </para>
-					<para>   <literal>Dial(${HINT})</literal></para>
-					<para>   <literal>Gosub(stdexten,${EXTEN},1(${HINT}))</literal></para>
-					<para> </para>
+					<example title="Valid expressions">
+					Dial(${HINT})
+					Gosub(stdexten,${EXTEN},1(${HINT}))
+					</example>
 					<para>Any extensions.conf style variables specified are passed directly to the
 					dialplan.</para>
 					<para> </para>
diff --git a/res/res_pjsip_dialog_info_body_generator.c b/res/res_pjsip_dialog_info_body_generator.c
index 88aa6c2dd20e967681050310fd9ecc0c3d2d30cd..9efb15492b2c7d75f37987a6a40872c96eafff75 100644
--- a/res/res_pjsip_dialog_info_body_generator.c
+++ b/res/res_pjsip_dialog_info_body_generator.c
@@ -183,7 +183,6 @@ static int dialog_info_generate_body_content(void *body, void *data)
 			int remote_connected_num_restricted;
 			char *local_caller_num;
 			pjsip_dialog *dlg = ast_sip_subscription_get_dialog(state_data->sub);
-			pjsip_sip_uri *dlg_remote_fromhdr = pjsip_uri_get_uri(dlg->local.info->uri);
 			char remote_target[PJSIP_MAX_URL_SIZE + 32];
 			char dlg_remote_uri[PJSIP_MAX_URL_SIZE];
 			char *from_domain_stripped;
@@ -191,7 +190,7 @@ static int dialog_info_generate_body_content(void *body, void *data)
 			pj_xml_node *remote_node, *remote_identity_node, *remote_target_node;
 
 			/* We use the local dialog URI to determine the domain to use in the XML itself */
-			ast_copy_pj_str(dlg_remote_uri, &dlg_remote_fromhdr->host, sizeof(dlg_remote_uri));
+			ast_copy_pj_str(dlg_remote_uri, ast_sip_pjsip_uri_get_hostname(dlg->local.info->uri), sizeof(dlg_remote_uri));
 			from_domain_stripped = ast_strip_quoted(dlg_remote_uri, "<", ">");
 			ast_sip_sanitize_xml(from_domain_stripped, from_domain_sanitized, sizeof(from_domain_sanitized));
 
@@ -234,7 +233,10 @@ static int dialog_info_generate_body_content(void *body, void *data)
 
 			pj_strdup2(state_data->pool, &remote_identity_node->content, remote_target);
 			if (!ast_strlen_zero(remote_cid_name)) {
-				ast_sip_presence_xml_create_attr(state_data->pool, remote_identity_node, "display", remote_cid_name);
+				char display_sanitized[PJSIP_MAX_URL_SIZE];
+
+				ast_sip_sanitize_xml(remote_cid_name, display_sanitized, sizeof(display_sanitized));
+				ast_sip_presence_xml_create_attr(state_data->pool, remote_identity_node, "display", display_sanitized);
 			}
 			ast_sip_presence_xml_create_attr(state_data->pool, remote_target_node, "uri", remote_target);
 		}
@@ -247,9 +249,13 @@ static int dialog_info_generate_body_content(void *body, void *data)
 			/* If a channel is not available we fall back to the sanitized local URI instead */
 			pj_strdup2(state_data->pool, &local_identity_node->content, S_OR(local_target, sanitized));
 			if (!ast_strlen_zero(local_cid_name)) {
-				ast_sip_presence_xml_create_attr(state_data->pool, local_identity_node, "display", local_cid_name);
+				char display_sanitized[PJSIP_MAX_URL_SIZE];
+
+				ast_sip_sanitize_xml(local_cid_name, display_sanitized, sizeof(display_sanitized));
+				ast_sip_presence_xml_create_attr(state_data->pool, local_identity_node, "display", display_sanitized);
 			}
-			ast_sip_presence_xml_create_attr(state_data->pool, local_target_node, "uri", S_OR(local_target, sanitized));
+
+			ast_sip_presence_xml_create_attr(state_data->pool, local_target_node, "uri", sanitized);
 		}
 	}
 
diff --git a/res/res_pjsip_diversion.c b/res/res_pjsip_diversion.c
index 1cc6e0827f1331519d3e48dccbcca7fdf213260d..b5191a98f27b5001be884cc7a3667b735e273d14 100644
--- a/res/res_pjsip_diversion.c
+++ b/res/res_pjsip_diversion.c
@@ -327,15 +327,15 @@ static void set_redirecting_reason_by_cause(pjsip_name_addr *name_addr,
 				   struct ast_party_redirecting_reason *data)
 {
 	static const pj_str_t cause_name = { "cause", 5 };
-	pjsip_sip_uri *uri = pjsip_uri_get_uri(name_addr);
+	pjsip_uri *uri = name_addr->uri;
 	pjsip_param *cause = NULL;
 	unsigned long cause_value = 0;
 
-	if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri)) {
+	if (!ast_sip_is_allowed_uri(uri)) {
 		return;
 	}
 
-	cause = pjsip_param_find(&uri->other_param, &cause_name);
+	cause = ast_sip_pjsip_uri_get_other_param(uri, &cause_name);
 
 	if (!cause) {
 		return;
@@ -507,7 +507,6 @@ static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirect
 
 	pjsip_fromto_hdr *hdr;
 	pjsip_name_addr *name_addr;
-	pjsip_sip_uri *uri;
 	pjsip_param *param;
 	pjsip_fromto_hdr *old_hdr;
 	const char *reason_str;
@@ -534,10 +533,9 @@ static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirect
 	hdr->sname = hdr->name = diversion_name;
 
 	name_addr = pjsip_uri_clone(tdata->pool, base);
-	uri = pjsip_uri_get_uri(name_addr->uri);
 
 	pj_strdup2(tdata->pool, &name_addr->display, id->name.str);
-	pj_strdup2(tdata->pool, &uri->user, id->number.str);
+	pj_strdup2(tdata->pool, (pj_str_t *)ast_sip_pjsip_uri_get_username(name_addr->uri), id->number.str);
 
 	param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
 	param->name = reason_name;
diff --git a/res/res_pjsip_endpoint_identifier_anonymous.c b/res/res_pjsip_endpoint_identifier_anonymous.c
index 63fc405620f1c809aa62ab3cd2a2d0b03928f8b8..cc4cd4bee13e41dc80d8fbc438f1144ffd94a8bb 100644
--- a/res/res_pjsip_endpoint_identifier_anonymous.c
+++ b/res/res_pjsip_endpoint_identifier_anonymous.c
@@ -33,12 +33,10 @@
 static int get_endpoint_details(pjsip_rx_data *rdata, char *domain, size_t domain_size)
 {
 	pjsip_uri *from = rdata->msg_info.from->uri;
-	pjsip_sip_uri *sip_from;
-	if (!PJSIP_URI_SCHEME_IS_SIP(from) && !PJSIP_URI_SCHEME_IS_SIPS(from)) {
+	if (!ast_sip_is_uri_sip_sips(from)) {
 		return -1;
 	}
-	sip_from = (pjsip_sip_uri *) pjsip_uri_get_uri(from);
-	ast_copy_pj_str(domain, &sip_from->host, domain_size);
+	ast_copy_pj_str(domain, ast_sip_pjsip_uri_get_hostname(from), domain_size);
 	return 0;
 }
 
diff --git a/res/res_pjsip_endpoint_identifier_user.c b/res/res_pjsip_endpoint_identifier_user.c
index 46e82db174c8ac8619c63ed3feb80e02990cf527..481cd3b3fcfb80327413123193cc2bee1ce1d0fb 100644
--- a/res/res_pjsip_endpoint_identifier_user.c
+++ b/res/res_pjsip_endpoint_identifier_user.c
@@ -32,14 +32,14 @@
 static int get_from_header(pjsip_rx_data *rdata, char *username, size_t username_size, char *domain, size_t domain_size)
 {
 	pjsip_uri *from = rdata->msg_info.from->uri;
-	pjsip_sip_uri *sip_from;
 
-	if (!PJSIP_URI_SCHEME_IS_SIP(from) && !PJSIP_URI_SCHEME_IS_SIPS(from)) {
+	if (!ast_sip_is_uri_sip_sips(from)) {
 		return -1;
 	}
-	sip_from = (pjsip_sip_uri *) pjsip_uri_get_uri(from);
-	ast_copy_pj_str(username, &sip_from->user, username_size);
-	ast_copy_pj_str(domain, &sip_from->host, domain_size);
+
+	ast_copy_pj_str(username, ast_sip_pjsip_uri_get_username(from), username_size);
+	ast_copy_pj_str(domain, ast_sip_pjsip_uri_get_hostname(from), domain_size);
+
 	return 0;
 }
 
diff --git a/res/res_pjsip_exten_state.c b/res/res_pjsip_exten_state.c
index b51df87b622287a2c12607d4933e33c7b8191c09..1b29091849ab0ca2a7d87a5e16c4899657c44c95 100644
--- a/res/res_pjsip_exten_state.c
+++ b/res/res_pjsip_exten_state.c
@@ -296,7 +296,8 @@ static int notify_task(void *obj)
 		.body_data = &task_data->exten_state_data,
 	};
 
-	/* Terminated subscriptions are no longer associated with a valid tree, and sending
+	/* The subscription was terminated while notify_task was in queue.
+	   Terminated subscriptions are no longer associated with a valid tree, and sending
 	 * NOTIFY messages on a subscription which has already been terminated won't work.
 	 */
 	if (ast_sip_subscription_is_terminated(task_data->exten_state_sub->sip_sub)) {
@@ -339,6 +340,13 @@ static int state_changed(const char *context, const char *exten,
 	struct notify_task_data *task_data;
 	struct exten_state_subscription *exten_state_sub = data;
 
+	/* Terminated subscriptions are no longer associated with a valid tree.
+	 * Do not queue notify_task.
+	 */
+	if (ast_sip_subscription_is_terminated(exten_state_sub->sip_sub)) {
+		return 0;
+	}
+
 	if (!(task_data = alloc_notify_task_data(exten, exten_state_sub, info))) {
 		return -1;
 	}
@@ -965,6 +973,7 @@ static int publisher_stop(struct ast_sip_outbound_publish_client *client)
 
 static int unload_module(void)
 {
+#if 0
 	ast_sip_unregister_event_publisher_handler(&dialog_publisher);
 	ast_sip_unregister_subscription_handler(&dialog_handler);
 	ast_sip_unregister_event_publisher_handler(&presence_publisher);
@@ -979,6 +988,18 @@ static int unload_module(void)
 	publishers = NULL;
 
 	return 0;
+#else
+	/* If we were allowed to unload, the above is what we would do.
+	 * pjsip_evsub_register_pkg is called by ast_sip_register_subscription_handler
+	 * but there is no corresponding unregister function, so unloading
+	 * a module does not remove the event package. If this module is ever
+	 * loaded again, then pjproject will assert and cause a crash.
+	 * For that reason, we must not be allowed to unload, but if
+	 * a pjsip_evsub_unregister_pkg API is added in the future
+	 * then we should go back to unloading the module as intended.
+	 */
+	return -1;
+#endif
 }
 
 static int load_module(void)
diff --git a/res/res_pjsip_geolocation.c b/res/res_pjsip_geolocation.c
new file mode 100644
index 0000000000000000000000000000000000000000..422c56d5f9a9421cbf6a56f93fd24009027f7e4d
--- /dev/null
+++ b/res/res_pjsip_geolocation.c
@@ -0,0 +1,666 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Sangoma Technologies Corporation
+ *
+ * George Joseph <gjoseph@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>res_geolocation</depend>
+	<depend>pjproject</depend>
+	<depend>res_pjsip</depend>
+	<depend>res_pjsip_session</depend>
+	<depend>chan_pjsip</depend>
+	<depend>libxml2</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+#include "asterisk/module.h"
+#include "asterisk/xml.h"
+#include "asterisk/res_geolocation.h"
+
+#include <pjsip_ua.h>
+#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_session.h"
+
+static pj_str_t GEOLOCATION_HDR;
+static pj_str_t GEOLOCATION_ROUTING_HDR;
+
+static int find_pidf(const char *session_name, struct pjsip_rx_data *rdata, char *geoloc_uri,
+	char **pidf_body, unsigned int *pidf_len)
+{
+	char *local_uri = ast_strdupa(geoloc_uri);
+	char *ra = NULL;
+	/*
+	 * If the URI is "cid" then we're going to search for a pidf document
+	 * in the body of the message.  If there's no body, there's no point.
+	 */
+	if (!rdata->msg_info.msg->body) {
+		ast_log(LOG_WARNING, "%s: There's no message body in which to search for '%s'.  Skipping\n",
+			session_name, geoloc_uri);
+		return -1;
+	}
+
+	if (local_uri[0] == '<') {
+		local_uri++;
+	}
+	ra = strchr(local_uri, '>');
+	if (ra) {
+		*ra = '\0';
+	}
+
+	/*
+	 * If the message content type is 'application/pidf+xml', then the pidf is
+	 * the only document in the message and we'll just parse the entire body
+	 * as xml.  If it's 'multipart/mixed' then we have to find the part that
+	 * has a Content-ID header value matching the URI.
+	 */
+	if (ast_sip_are_media_types_equal(&rdata->msg_info.ctype->media,
+		&pjsip_media_type_application_pidf_xml)) {
+		*pidf_body = rdata->msg_info.msg->body->data;
+		*pidf_len = rdata->msg_info.msg->body->len;
+	} else if (ast_sip_are_media_types_equal(&rdata->msg_info.ctype->media,
+		&pjsip_media_type_multipart_mixed)) {
+		pj_str_t cid = pj_str(local_uri);
+		pjsip_multipart_part *mp = pjsip_multipart_find_part_by_cid_str(
+			rdata->tp_info.pool, rdata->msg_info.msg->body, &cid);
+
+		if (!mp) {
+			ast_log(LOG_WARNING, "%s: A Geolocation header was found with URI '%s'"
+				" but the associated multipart part was not found in the message body.  Skipping URI",
+				session_name, geoloc_uri);
+			return -1;
+		}
+		*pidf_body = mp->body->data;
+		*pidf_len = mp->body->len;
+	} else {
+		ast_log(LOG_WARNING, "%s: A Geolocation header was found with URI '%s'"
+			" but no pidf document with that content id was found.  Skipping URI",
+			session_name, geoloc_uri);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int add_eprofile_to_channel(struct ast_sip_session *session,
+	struct ast_geoloc_eprofile *eprofile, struct ast_str * buf)
+{
+	const char *session_name = (session ? ast_sip_session_get_name(session) : "NULL_SESSION");
+	struct ast_datastore *ds = NULL;
+	int rc = 0;
+	SCOPE_ENTER(4, "%s\n", session_name);
+
+	ds = ast_geoloc_datastore_create(session_name);
+	if (!ds) {
+		SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING,
+			"%s: Couldn't allocate a geoloc datastore\n", session_name);
+	}
+
+	/*
+	 * We want the datastore to pass through the dialplan and the core
+	 * so we need to turn inheritance on.
+	 */
+	ast_geoloc_datastore_set_inheritance(ds, 1);
+
+	rc = ast_geoloc_datastore_add_eprofile(ds, eprofile);
+	if (rc <= 0) {
+		ast_datastore_free(ds);
+		SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING,
+			"%s: Couldn't add eprofile '%s' to datastore\n", session_name,
+			eprofile->id);
+	}
+
+	ast_channel_lock(session->channel);
+	ast_channel_datastore_add(session->channel, ds);
+	ast_channel_unlock(session->channel);
+
+	SCOPE_EXIT_RTN_VALUE(0, "%s: eprofile: '%s' EffectiveLoc: %s\n",
+		session_name, eprofile->id, ast_str_buffer(
+		ast_variable_list_join(eprofile->effective_location, ",", "=", NULL, &buf)));
+}
+
+static int handle_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+	const char *session_name = (session ? ast_sip_session_get_name(session) : "NULL_SESSION");
+	struct ast_sip_endpoint *endpoint = (session ? session->endpoint : NULL);
+	struct ast_channel *channel = (session ? session->channel : NULL);
+	RAII_VAR(struct ast_geoloc_profile *, config_profile, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_geoloc_eprofile *, config_eprofile, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_datastore *, ds, NULL, ast_datastore_free);
+	RAII_VAR(struct ast_geoloc_eprofile *, incoming_eprofile, NULL, ao2_cleanup);
+	char *geoloc_hdr_value = NULL;
+	char *geoloc_routing_hdr_value = NULL;
+	char *geoloc_uri = NULL;
+	int rc = 0;
+	RAII_VAR(struct ast_str *, buf, NULL, ast_free);
+	pjsip_generic_string_hdr *geoloc_hdr = NULL;
+	pjsip_generic_string_hdr *geoloc_routing_hdr = NULL;
+	SCOPE_ENTER(3, "%s\n", session_name);
+
+	if (!session) {
+		SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING, "%s: session is NULL!!!.  Skipping.\n",
+			session_name);
+	}
+	if (!endpoint) {
+		SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING, "%s: Session has no endpoint.  Skipping.\n",
+			session_name);
+	}
+
+	if (!channel) {
+		SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING, "%s: Session has no channel.  Skipping.\n",
+			session_name);
+	}
+
+	if (!rdata) {
+		SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING, "%s: Session has no rdata.  Skipping.\n",
+			session_name);
+	}
+
+	/*
+	 * We don't need geoloc_hdr or geoloc_routing_hdr for a while but we get it now
+	 * for trace purposes.
+	 */
+	geoloc_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &GEOLOCATION_HDR, NULL);
+	geoloc_routing_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
+		&GEOLOCATION_ROUTING_HDR, NULL);
+
+	if (!geoloc_hdr) {
+		ast_trace(4, "%s: Message has no Geolocation header\n", session_name);
+	} else {
+		ast_trace(4, "%s: Geolocation: " PJSTR_PRINTF_SPEC "\n", session_name,
+			PJSTR_PRINTF_VAR(geoloc_hdr->hvalue));
+	}
+
+	if (ast_strlen_zero(endpoint->geoloc_incoming_call_profile)) {
+		if (geoloc_hdr) {
+			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE, "%s: Message has Geolocation header '"
+				PJSTR_PRINTF_SPEC "' but endpoint has no geoloc_incoming_call_profile. "
+				"Done.\n", session_name,
+				PJSTR_PRINTF_VAR(geoloc_hdr->hvalue));
+		} else {
+			SCOPE_EXIT_RTN_VALUE(0, "%s: Endpoint has no geoloc_incoming_call_profile. "
+				"Done.\n", session_name);
+		}
+	}
+
+	config_profile = ast_geoloc_get_profile(endpoint->geoloc_incoming_call_profile);
+	if (!config_profile) {
+		if (geoloc_hdr) {
+			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE, "%s: Message has Geolocation header '"
+				PJSTR_PRINTF_SPEC "' but endpoint's geoloc_incoming_call_profile doesn't exist. "
+				"Done.\n", session_name,
+				PJSTR_PRINTF_VAR(geoloc_hdr->hvalue));
+		} else {
+			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_NOTICE, "%s: Message has no Geolocation header and endpoint has "
+				" an invalid geoloc_incoming_call_profile. Done.\n", session_name);
+		}
+	}
+
+	buf = ast_str_create(1024);
+	if (!buf) {
+		SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING, "%s: Unable to allocate buf\n", session_name);
+	}
+
+	if (config_profile->precedence != AST_GEOLOC_PRECED_DISCARD_CONFIG) {
+		config_eprofile = ast_geoloc_eprofile_create_from_profile(config_profile);
+		if (!config_eprofile) {
+			ast_log(LOG_WARNING, "%s: Unable to create config_eprofile from "
+				"profile '%s'\n", session_name, ast_sorcery_object_get_id(config_profile));
+		}
+
+		if (config_eprofile && config_eprofile->effective_location) {
+			ast_trace(4, "%s: config eprofile '%s' has effective location\n",
+				session_name, config_eprofile->id);
+
+			if (!geoloc_hdr || config_profile->precedence == AST_GEOLOC_PRECED_DISCARD_INCOMING ||
+				config_profile->precedence == AST_GEOLOC_PRECED_PREFER_CONFIG) {
+
+				ast_trace(4, "%s: config eprofile '%s' is being used\n",
+					session_name, config_eprofile->id);
+
+				/*
+				 * If we have an effective location and there's no geolocation header,
+				 * or the action is either DISCARD_INCOMING or PREFER_CONFIG,
+				 * we don't need to even look for a Geolocation header so just add the
+				 * config eprofile to the channel and exit.
+				 */
+
+				rc = add_eprofile_to_channel(session, config_eprofile, buf);
+				if (rc != 0) {
+					SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING,
+						"%s: Couldn't add config eprofile '%s' to datastore. Fail.\n", session_name,
+						config_eprofile->id);
+				}
+
+				SCOPE_EXIT_RTN_VALUE(0, "%s: Added geoloc datastore with eprofile from config. Done.\n",
+					session_name);
+			}
+		} else {
+			/*
+			 * If the config eprofile has no effective location, just get rid
+			 * of it.
+			 */
+			ast_trace(4, "%s: Either config_eprofile didn't exist or it had no effective location\n",
+				session_name);
+
+			ao2_cleanup(config_eprofile);
+			config_eprofile = NULL;
+			if (config_profile->precedence == AST_GEOLOC_PRECED_DISCARD_INCOMING) {
+				SCOPE_EXIT_RTN_VALUE(0, "%s: DISCARD_INCOMING set and no config eprofile. Done.\n",
+					session_name);
+			}
+		}
+	}
+
+	/*
+	 * At this point, if we have a config_eprofile, then the action was
+	 * PREFER_INCOMING so we're going to keep it as a backup if we can't
+	 * get a profile from the incoming message.
+	 */
+
+	if (geoloc_hdr && config_profile->precedence != AST_GEOLOC_PRECED_DISCARD_INCOMING) {
+
+		/*
+		 * From RFC-6442:
+		 * Geolocation-header = "Geolocation" HCOLON locationValue
+		 *                      *( COMMA locationValue )
+		 * locationValue      = LAQUOT locationURI RAQUOT
+		 *                      *(SEMI geoloc-param)
+		 * locationURI        = sip-URI / sips-URI / pres-URI
+		 *                        / http-URI / https-URI
+		 *	                      / cid-url ; (from RFC 2392)
+		 *                        / absoluteURI ; (from RFC 3261)
+		 */
+
+		geoloc_hdr_value = ast_alloca(geoloc_hdr->hvalue.slen + 1);
+		ast_copy_pj_str(geoloc_hdr_value, &geoloc_hdr->hvalue, geoloc_hdr->hvalue.slen + 1);
+
+		/*
+		 * We're going to scan the header value for URIs until we find
+		 * one that processes successfully or we run out of URIs.
+		 * I.E.  The first good one wins.
+		 */
+		while (geoloc_hdr_value && !incoming_eprofile) {
+			char *pidf_body = NULL;
+			unsigned int pidf_len = 0;
+			struct ast_xml_doc *incoming_doc = NULL;
+			int rc = 0;
+
+			/* We're only going to consider the first URI in the header for now */
+			geoloc_uri = ast_strsep(&geoloc_hdr_value, ',', AST_STRSEP_TRIM);
+			if (ast_strlen_zero(geoloc_uri) || geoloc_uri[0] != '<' || strchr(geoloc_uri, '>') == NULL) {
+				ast_log(LOG_WARNING, "%s: Geolocation header has no or bad URI '%s'.  Skipping\n", session_name,
+					S_OR(geoloc_uri, "<empty>"));
+				continue;
+			}
+
+			ast_trace(4, "Processing URI '%s'\n", geoloc_uri);
+
+			if (!ast_begins_with(geoloc_uri, "<cid:")) {
+				ast_trace(4, "Processing URI '%s'\n", geoloc_uri);
+
+				incoming_eprofile = ast_geoloc_eprofile_create_from_uri(geoloc_uri, session_name);
+				if (!incoming_eprofile) {
+					ast_log(LOG_WARNING, "%s: Unable to create effective profile for URI '%s'.  Skipping\n",
+						session_name, geoloc_uri);
+					continue;
+				}
+			} else {
+				ast_trace(4, "Processing PIDF-LO '%s'\n", geoloc_uri);
+
+				rc = find_pidf(session_name, rdata, geoloc_uri, &pidf_body, &pidf_len);
+				if (rc != 0 || !pidf_body || pidf_len == 0) {
+					continue;
+				}
+				ast_trace(5, "Processing PIDF-LO "PJSTR_PRINTF_SPEC "\n", (int)pidf_len, pidf_body);
+
+				incoming_doc = ast_xml_read_memory(pidf_body, pidf_len);
+				if (!incoming_doc) {
+					ast_log(LOG_WARNING, "%s: Unable to parse pidf document for URI '%s'\n",
+						session_name, geoloc_uri);
+					continue;
+				}
+
+				incoming_eprofile = ast_geoloc_eprofile_create_from_pidf(incoming_doc, geoloc_uri, session_name);
+				ast_xml_close(incoming_doc);
+
+				if (!incoming_eprofile) {
+					ast_log(LOG_WARNING,
+						"%s: Couldn't create incoming_eprofile from pidf\n", session_name);
+					continue;
+				}
+			}
+		}
+	}
+
+	if (!incoming_eprofile) {
+		/* Use the config_eprofile as a backup if there was one */
+		incoming_eprofile = config_eprofile;
+	} else {
+		ao2_cleanup(config_eprofile);
+		config_eprofile = NULL;
+		if (geoloc_routing_hdr) {
+			geoloc_routing_hdr_value = ast_alloca(geoloc_routing_hdr->hvalue.slen + 1);
+			ast_copy_pj_str(geoloc_routing_hdr_value, &geoloc_routing_hdr->hvalue,
+				geoloc_routing_hdr->hvalue.slen + 1);
+			incoming_eprofile->allow_routing_use = ast_true(geoloc_routing_hdr_value);
+		}
+	}
+
+	if (incoming_eprofile) {
+		rc = add_eprofile_to_channel(session, incoming_eprofile, buf);
+		if (rc != 0) {
+			SCOPE_EXIT_LOG_RTN_VALUE(0, LOG_WARNING,
+				"%s: Couldn't add eprofile '%s' to channel. Fail.\n", session_name,
+				incoming_eprofile->id);
+		}
+
+		SCOPE_EXIT_RTN_VALUE(0, "%s: Added eprofile '%s' to channel. Done.\n",
+			session_name, incoming_eprofile->id);
+	}
+
+	SCOPE_EXIT_RTN_VALUE(0, "%s: No eprofiles to add to channel. Done.\n",	session_name);
+}
+
+static const char *add_eprofile_to_tdata(struct ast_geoloc_eprofile *eprofile, struct ast_channel *channel,
+	struct pjsip_tx_data *tdata, struct ast_str **buf, const char *session_name)
+{
+	static const pj_str_t from_name = { "From", 4};
+	static const pj_str_t cid_name = { "Content-ID", 10 };
+
+	pjsip_sip_uri *sip_uri;
+	pjsip_generic_string_hdr *cid;
+	pj_str_t cid_value;
+	pjsip_from_hdr *from = pjsip_msg_find_hdr_by_name(tdata->msg, &from_name, NULL);
+	pjsip_sdp_info *tdata_sdp_info;
+	pjsip_msg_body *multipart_body = NULL;
+	pjsip_multipart_part *pidf_part;
+	pj_str_t pidf_body_text;
+	char id[6];
+	size_t alloc_size;
+	RAII_VAR(char *, base_cid, NULL, ast_free);
+	const char *final_doc;
+	int rc = 0;
+	SCOPE_ENTER(3, "%s\n", session_name);
+
+	/*
+	 * ast_geoloc_eprofiles_to_pidf() takes the datastore with all of the eprofiles
+	 * in it, skips over the ones not needing PIDF processing and combines the
+	 * rest into one document.
+	 */
+	final_doc = ast_geoloc_eprofile_to_pidf(eprofile, channel, buf, session_name);
+	ast_trace(5, "Final pidf: \n%s\n", final_doc);
+
+	if (!final_doc) {
+		SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create pidf document from"
+			" eprofile '%s'\n\n", session_name, eprofile->id);
+	}
+
+	/*
+	 * There _should_ be an SDP already attached to the tdata at this point
+	 * but maybe not.  If we can find an existing one, we'll convert the tdata
+	 * body into a multipart body and add the SDP as the first part.  Then we'll
+	 * create another part to hold the PIDF.
+	 *
+	 * If we don't find one, we're going to create an empty multipart body
+	 * and add the PIDF part to it.
+	 *
+	 * Technically, if we only have the PIDF, we don't need a multipart
+	 * body to hold it but that means we'd have to add the Content-ID header
+	 * to the main SIP message.  Since it's unlikely, it's just better to
+	 * add the multipart body and leave the rest of the processing unchanged.
+	 */
+	tdata_sdp_info = pjsip_tdata_get_sdp_info(tdata);
+	if (tdata_sdp_info->sdp) {
+		ast_trace(4, "body: %p %u\n", tdata_sdp_info->sdp, (unsigned)tdata_sdp_info->sdp_err);
+
+		rc = pjsip_create_multipart_sdp_body(tdata->pool, tdata_sdp_info->sdp, &multipart_body);
+		if (rc != PJ_SUCCESS) {
+			SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Unable to create sdp multipart body\n",
+				session_name);
+		}
+	} else {
+	    multipart_body = pjsip_multipart_create(tdata->pool, &pjsip_media_type_multipart_mixed, NULL);
+	}
+
+	pidf_part = pjsip_multipart_create_part(tdata->pool);
+	pj_cstr(&pidf_body_text, final_doc);
+	pidf_part->body = pjsip_msg_body_create(tdata->pool, &pjsip_media_type_application_pidf_xml.type,
+		&pjsip_media_type_application_pidf_xml.subtype, &pidf_body_text);
+
+    pjsip_multipart_add_part(tdata->pool, multipart_body, pidf_part);
+
+	sip_uri = (pjsip_sip_uri *)pjsip_uri_get_uri(from->uri);
+	alloc_size = sizeof(id) + pj_strlen(&sip_uri->host) + 2;
+	base_cid = ast_malloc(alloc_size);
+	sprintf(base_cid, "%s@%.*s",
+			ast_generate_random_string(id, sizeof(id)),
+			(int) pj_strlen(&sip_uri->host), pj_strbuf(&sip_uri->host));
+
+	ast_str_set(buf, 0, "cid:%s", base_cid);
+	ast_trace(4, "cid: '%s' uri: '%s'\n", base_cid, ast_str_buffer(*buf));
+
+	cid_value.ptr = pj_pool_alloc(tdata->pool, alloc_size);
+	cid_value.slen = sprintf(cid_value.ptr, "<%s>", base_cid);
+
+	cid = pjsip_generic_string_hdr_create(tdata->pool, &cid_name, &cid_value);
+
+	pj_list_insert_after(&pidf_part->hdr, cid);
+
+    tdata->msg->body = multipart_body;
+
+	SCOPE_EXIT_RTN_VALUE(ast_str_buffer(*buf), "%s: PIDF-LO added with cid '%s'\n", session_name, base_cid);
+}
+
+static void handle_outgoing_request(struct ast_sip_session *session, struct pjsip_tx_data *tdata)
+{
+	const char *session_name = ast_sip_session_get_name(session);
+	struct ast_sip_endpoint *endpoint = session->endpoint;
+	struct ast_channel *channel = session->channel;
+	RAII_VAR(struct ast_geoloc_profile *, config_profile, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_geoloc_eprofile *, config_eprofile, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_geoloc_eprofile *, incoming_eprofile, NULL, ao2_cleanup);
+	struct ast_geoloc_eprofile *final_eprofile = NULL;
+	RAII_VAR(struct ast_str *, buf, NULL, ast_free);
+	struct ast_datastore *ds = NULL;  /* The channel cleans up ds */
+	pjsip_msg_body *orig_body = NULL;
+	pjsip_generic_string_hdr *geoloc_hdr = NULL;
+	int eprofile_count = 0;
+	int rc = 0;
+	const char *uri;
+	SCOPE_ENTER(3, "%s\n", session_name);
+
+	if (!endpoint) {
+		SCOPE_EXIT_LOG_RTN(LOG_WARNING, "%s: Session has no endpoint.  Skipping.\n",
+			session_name);
+	}
+
+	if (!channel) {
+		SCOPE_EXIT_LOG_RTN(LOG_WARNING, "%s: Session has no channel.  Skipping.\n",
+			session_name);
+	}
+
+	if (ast_strlen_zero(endpoint->geoloc_outgoing_call_profile)) {
+		SCOPE_EXIT_RTN("%s: Endpoint has no geoloc_outgoing_call_profile. Skipping.\n",
+			session_name);
+	}
+
+	config_profile = ast_geoloc_get_profile(endpoint->geoloc_outgoing_call_profile);
+	if (!config_profile) {
+		SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Endpoint's geoloc_outgoing_call_profile doesn't exist. "
+			"Geolocation info discarded.\n", session_name);
+	}
+
+	config_eprofile = ast_geoloc_eprofile_create_from_profile(config_profile);
+	if (!config_eprofile) {
+		SCOPE_EXIT_LOG_RTN(LOG_WARNING, "%s: Unable to create eprofile from "
+			"profile '%s'\n", session_name, ast_sorcery_object_get_id(config_profile));
+	}
+
+	if (!config_eprofile->effective_location) {
+		/*
+		 * If there's no effective location on the eprofile
+		 * we don't need to keep it.
+		 */
+		ast_trace(4, "%s: There was no effective location for config profile '%s'\n",
+			session_name, ast_sorcery_object_get_id(config_profile));
+		ao2_ref(config_eprofile, -1);
+		config_eprofile = NULL;
+	}
+
+	ds = ast_geoloc_datastore_find(channel);
+	if (!ds) {
+		ast_trace(4, "%s: There was no geoloc datastore on the channel\n", session_name);
+	} else {
+		eprofile_count = ast_geoloc_datastore_size(ds);
+		ast_trace(4, "%s: There are %d geoloc profiles on this channel\n", session_name,
+			eprofile_count);
+		/*
+		 * There'd better be a max of 1 at this time.  In the future
+		 * we may allow more than 1.
+		 */
+		incoming_eprofile = ast_geoloc_datastore_get_eprofile(ds, 0);
+	}
+
+	ast_trace(4, "%s: Profile precedence: %s\n\n", session_name,
+		ast_geoloc_precedence_to_name(config_profile->precedence));
+
+	switch (config_profile->precedence) {
+	case AST_GEOLOC_PRECED_DISCARD_INCOMING:
+		final_eprofile = config_eprofile;
+		ao2_cleanup(incoming_eprofile);
+		incoming_eprofile = NULL;
+		break;
+	case AST_GEOLOC_PRECED_PREFER_INCOMING:
+		if (incoming_eprofile) {
+			final_eprofile = incoming_eprofile;
+			ao2_cleanup(config_eprofile);
+			config_eprofile = NULL;
+		} else {
+			final_eprofile = config_eprofile;
+		}
+		break;
+	case AST_GEOLOC_PRECED_DISCARD_CONFIG:
+		final_eprofile = incoming_eprofile;
+		ao2_cleanup(config_eprofile);
+		config_eprofile = NULL;
+		break;
+	case AST_GEOLOC_PRECED_PREFER_CONFIG:
+		if (config_eprofile) {
+			final_eprofile = config_eprofile;
+			ao2_cleanup(incoming_eprofile);
+			incoming_eprofile = NULL;
+		} else {
+			final_eprofile = incoming_eprofile;
+		}
+		break;
+	}
+
+	if (!final_eprofile) {
+		SCOPE_EXIT_RTN("%s: No eprofiles to send.  Done.\n",
+			session_name);
+	}
+
+	if (!final_eprofile->effective_location) {
+		ast_geoloc_eprofile_refresh_location(final_eprofile);
+	}
+
+	buf = ast_str_create(1024);
+	if (!buf) {
+		SCOPE_EXIT_LOG_RTN(LOG_WARNING, "%s: Unable to allocate buf\n", session_name);
+	}
+
+	if (final_eprofile->format == AST_GEOLOC_FORMAT_URI) {
+		uri = ast_geoloc_eprofile_to_uri(final_eprofile, channel, &buf, session_name);
+		if (!uri) {
+			SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Unable to create URI from eprofile '%s'\n",
+				session_name, final_eprofile->id);
+		}
+	} else {
+		orig_body = tdata->msg->body;
+		uri = add_eprofile_to_tdata(final_eprofile, channel, tdata, &buf, session_name);
+		if (!uri) {
+			tdata->msg->body = orig_body;
+			SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Unable to add eprofile '%s' to tdata\n",
+				session_name, final_eprofile->id);
+		}
+	}
+
+	uri = ast_strdupa(ast_str_buffer(buf));
+	ast_str_reset(buf);
+	ast_str_set(&buf, 0, "<%s>", uri);
+	uri = ast_strdupa(ast_str_buffer(buf));
+
+	ast_trace(4, "%s: Using URI '%s'\n", session_name, uri);
+
+	/* It's almost impossible for add header to fail but you never know */
+	geoloc_hdr = ast_sip_add_header2(tdata, "Geolocation", uri);
+	if (geoloc_hdr == NULL) {
+		if (orig_body) {
+			tdata->msg->body = orig_body;
+		}
+		SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Unable to add Geolocation header\n", session_name);
+	}
+	rc = ast_sip_add_header(tdata, "Geolocation-Routing", final_eprofile->allow_routing_use ? "yes" : "no");
+	if (rc != 0) {
+		if (orig_body) {
+			tdata->msg->body = orig_body;
+		}
+		pj_list_erase(geoloc_hdr);
+		SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Unable to add Geolocation-Routing header\n", session_name);
+	}
+	SCOPE_EXIT_RTN("%s: Geolocation: %s\n", session_name, uri);
+}
+
+static struct ast_sip_session_supplement geolocation_supplement = {
+	.method = "INVITE",
+	.priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL + 10,
+	.incoming_request = handle_incoming_request,
+	.outgoing_request = handle_outgoing_request,
+};
+
+static int reload_module(void)
+{
+	return 0;
+}
+
+static int unload_module(void)
+{
+	int res = 0;
+	ast_sip_session_unregister_supplement(&geolocation_supplement);
+
+	return res;
+}
+
+static int load_module(void)
+{
+	int res = 0;
+	GEOLOCATION_HDR = pj_str("Geolocation");
+	GEOLOCATION_ROUTING_HDR = pj_str("Geolocation-Routing");
+
+	ast_sip_session_register_supplement(&geolocation_supplement);
+
+	return res;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "res_pjsip_geolocation Module for Asterisk",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.reload = reload_module,
+	.load_pri = AST_MODPRI_CHANNEL_DEPEND - 1,
+	.requires = "res_geolocation,res_pjsip,res_pjsip_session,chan_pjsip",
+);
diff --git a/res/res_pjsip_header_funcs.c b/res/res_pjsip_header_funcs.c
index ac3669583a7dc76823462f5f4ed3314e8675e17a..0b92289f08c7e995829314f75afbf016cadf758a 100644
--- a/res/res_pjsip_header_funcs.c
+++ b/res/res_pjsip_header_funcs.c
@@ -4,6 +4,8 @@
  * Copyright (C) 2013, Fairview 5 Engineering, LLC
  *
  * George Joseph <george.joseph@fairview5.com>
+ * José Lopes <jose.lopes@nfon.com>
+ * Naveen Albert <asterisk@phreaknet.org>
  *
  * See http://www.asterisk.org for more information about
  * the Asterisk project. Please do not directly contact
@@ -84,68 +86,67 @@
 			channel. One exception is that you can read headers that you have already
 			added on the outbound channel.</para>
 			<para>Examples:</para>
-			<para>;</para>
-			<para>; Set 'somevar' to the value of the 'From' header.</para>
-			<para>exten => 1,1,Set(somevar=${PJSIP_HEADER(read,From)})</para>
-			<para>;</para>
-			<para>; Set 'via2' to the value of the 2nd 'Via' header.</para>
-			<para>exten => 1,1,Set(via2=${PJSIP_HEADER(read,Via,2)})</para>
-			<para>;</para>
-			<para>; Set 'xhdr' to the value of the 1sx X-header.</para>
-			<para>exten => 1,1,Set(xhdr=${PJSIP_HEADER(read,X-*,1)})</para>
-			<para>;</para>
-			<para>; Add an 'X-Myheader' header with the value of 'myvalue'.</para>
-			<para>exten => 1,1,Set(PJSIP_HEADER(add,X-MyHeader)=myvalue)</para>
-			<para>;</para>
-			<para>; Add an 'X-Myheader' header with an empty value.</para>
-			<para>exten => 1,1,Set(PJSIP_HEADER(add,X-MyHeader)=)</para>
-			<para>;</para>
-			<para>; Update the value of the header named 'X-Myheader' to 'newvalue'.</para>
-			<para>; 'X-Myheader' must already exist or the call will fail.</para>
-			<para>exten => 1,1,Set(PJSIP_HEADER(update,X-MyHeader)=newvalue)</para>
-			<para>;</para>
-			<para>; Remove all headers whose names exactly match 'X-MyHeader'.</para>
-			<para>exten => 1,1,Set(PJSIP_HEADER(remove,X-MyHeader)=)</para>
-			<para>;</para>
-			<para>; Remove all headers that begin with 'X-My'.</para>
-			<para>exten => 1,1,Set(PJSIP_HEADER(remove,X-My*)=)</para>
-			<para>;</para>
-			<para>; Remove all previously added headers.</para>
-			<para>exten => 1,1,Set(PJSIP_HEADER(remove,*)=)</para>
-			<para>;</para>
-
+			<example title="Set somevar to the value of the From header">
+			exten => 1,1,Set(somevar=${PJSIP_HEADER(read,From)})
+			</example>
+			<example title="Set via2 to the value of the 2nd Via header">
+			exten => 1,1,Set(via2=${PJSIP_HEADER(read,Via,2)})
+			</example>
+			<example title="Set xhdr to the value of the 1st X-header">
+			exten => 1,1,Set(xhdr=${PJSIP_HEADER(read,X-*,1)})
+			</example>
+			<example title="Add an X-Myheader header with the value of myvalue">
+			exten => 1,1,Set(PJSIP_HEADER(add,X-MyHeader)=myvalue)
+			</example>
+			<example title="Add an X-Myheader header with an empty value">
+			exten => 1,1,Set(PJSIP_HEADER(add,X-MyHeader)=)
+			</example>
+			<example title="Update the value of the header named X-Myheader to newvalue">
+			; 'X-Myheader' must already exist or the call will fail.
+			exten => 1,1,Set(PJSIP_HEADER(update,X-MyHeader)=newvalue)
+			</example>
+			<example title="Remove all headers whose names exactly match X-MyHeader">
+			exten => 1,1,Set(PJSIP_HEADER(remove,X-MyHeader)=)
+			</example>
+			<example title="Remove all headers that begin with X-My">
+			exten => 1,1,Set(PJSIP_HEADER(remove,X-My*)=)
+			</example>
+			<example title="Remove all previously added headers">
+			exten => 1,1,Set(PJSIP_HEADER(remove,*)=)
+			</example>
 			<note><para>The <literal>remove</literal> action can be called by reading
-			<emphasis>or</emphasis> writing PJSIP_HEADER.</para>
-			<para>;</para>
-			<para>; Display the number of headers removed</para>
-			<para>exten => 1,1,Verbose( Removed ${PJSIP_HEADER(remove,X-MyHeader)} headers)</para>
-			<para>;</para>
-			<para>; Set a variable to the number of headers removed</para>
-			<para>exten => 1,1,Set(count=${PJSIP_HEADER(remove,X-MyHeader)})</para>
-			<para>;</para>
-			<para>; Just remove them ignoring any count</para>
-			<para>exten => 1,1,Set(=${PJSIP_HEADER(remove,X-MyHeader)})</para>
-			<para>exten => 1,1,Set(PJSIP_HEADER(remove,X-MyHeader)=)</para>
-			<para>;</para>
-			</note>
+			<emphasis>or</emphasis> writing PJSIP_HEADER.</para></note>
+			<example title="Display the number of headers removed">
+			exten => 1,1,Verbose( Removed ${PJSIP_HEADER(remove,X-MyHeader)} headers)
+			</example>
+			<example title="Set a variable to the number of headers removed">
+			exten => 1,1,Set(count=${PJSIP_HEADER(remove,X-MyHeader)})
+			</example>
+			<example title="Just remove them ignoring any count">
+			exten => 1,1,Set(=${PJSIP_HEADER(remove,X-MyHeader)})
+			exten => 1,1,Set(PJSIP_HEADER(remove,X-MyHeader)=)
+			</example>
 
 			<note><para>If you call PJSIP_HEADER in a normal dialplan context you'll be
 			operating on the <emphasis>caller's (incoming)</emphasis> channel which
 			may not be what you want. To operate on the <emphasis>callee's (outgoing)</emphasis>
-			channel call PJSIP_HEADER in a pre-dial handler. </para>
-			<para>Example:</para>
-			<para>;</para>
-			<para>[handler]</para>
-			<para>exten => addheader,1,Set(PJSIP_HEADER(add,X-MyHeader)=myvalue)</para>
-			<para>exten => addheader,2,Set(PJSIP_HEADER(add,X-MyHeader2)=myvalue2)</para>
-			<para>;</para>
-			<para>[somecontext]</para>
-			<para>exten => 1,1,Dial(PJSIP/${EXTEN},,b(handler^addheader^1))</para>
-			<para>;</para>
-			</note>
+			channel call PJSIP_HEADER in a pre-dial handler. </para></note>
+			<example title="Set headers on callee channel">
+			[handler]
+			exten => addheader,1,Set(PJSIP_HEADER(add,X-MyHeader)=myvalue)
+			exten => addheader,2,Set(PJSIP_HEADER(add,X-MyHeader2)=myvalue2)
+
+			[somecontext]
+			exten => 1,1,Dial(PJSIP/${EXTEN},,b(handler^addheader^1))
+			</example>
 		</description>
 	</function>
 	<function name="PJSIP_HEADERS" language="en_US">
+		<since>
+			<version>16.20.0</version>
+			<version>18.6.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Gets the list of SIP header names from an INVITE message.
 		</synopsis>
@@ -167,7 +168,136 @@
 			<ref type="function">PJSIP_HEADER</ref>
 		</see-also>
 	</function>
+	<function name="PJSIP_RESPONSE_HEADER" language="en_US">
+		<synopsis>
+			Gets headers of 200 response from an outbound PJSIP channel.
+		</synopsis>
+		<syntax>
+			<parameter name="action" required="true">
+				<enumlist>
+					<enum name="read">
+						<para>Returns instance <replaceable>number</replaceable>
+						of response header <replaceable>name</replaceable>.</para>
+					</enum>
+				</enumlist>
+			</parameter>
+
+			<parameter name="name" required="true">
+				<para>The <replaceable>name</replaceable> of the response header.
+				A <literal>*</literal> can be appended to the <replaceable>name</replaceable>
+				to iterate over all response headers <emphasis>beginning with</emphasis>
+				<replaceable>name</replaceable>.</para>
+			</parameter>
+
+			<parameter name="number" required="false">
+				<para>If there's more than 1 header with the same name, this specifies which header
+				to read.  If not specified, defaults to <literal>1</literal> meaning
+				the first matching header.
+				</para>
+			</parameter>
 
+		</syntax>
+		<description>
+			<para>PJSIP_RESPONSE_HEADER allows you to read specific SIP headers of 200 response
+			from the outbound PJSIP channel.</para>
+			<para>Examples:</para>
+			<example title="Set 'somevar' to the value of the 'From' header">
+				exten => 1,1,Set(somevar=${PJSIP_RESPONSE_HEADER(read,From)})
+			</example>
+			<example title="Set 'via2' to the value of the 2nd 'Via' header">
+				exten => 1,1,Set(via2=${PJSIP_RESPONSE_HEADER(read,Via,2)})
+			</example>
+			<example title="Set 'xhdr' to the value of the 1sx X-header">
+				exten => 1,1,Set(xhdr=${PJSIP_RESPONSE_HEADER(read,X-*,1)})
+			</example>
+
+			<note><para>If you call PJSIP_RESPONSE_HEADER in a normal dialplan context you'll be
+			operating on the <emphasis>caller's (incoming)</emphasis> channel which
+			may not be what you want. To operate on the <emphasis>callee's (outgoing)</emphasis>
+			channel call PJSIP_RESPONSE_HEADER in a pre-connect handler.</para>
+			</note>
+			<example title="Usage on pre-connect handler">
+				[handler]
+				exten => readheader,1,NoOp(PJSIP_RESPONSE_HEADER(read,X-MyHeader))
+				[somecontext]
+				exten => 1,1,Dial(PJSIP/${EXTEN},,U(handler^readheader^1))
+			</example>
+		</description>
+		<see-also>
+			<ref type="function">PJSIP_RESPONSE_HEADERS</ref>
+			<ref type="function">PJSIP_HEADER</ref>
+		</see-also>
+	</function>
+	<function name="PJSIP_RESPONSE_HEADERS" language="en_US">
+		<synopsis>
+			Gets the list of SIP header names from the 200 response of INVITE message.
+		</synopsis>
+		<syntax>
+			<parameter name="prefix">
+				<para>If specified, only the headers matching the given prefix are returned.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Returns a comma-separated list of header names (without values) from the 200
+			response of INVITE message. Multiple headers with the same name are included in the
+			list only once.</para>
+			<para>For example, <literal>${PJSIP_RESPONSE_HEADERS(Co)}</literal> might return
+			<literal>Contact,Content-Length,Content-Type</literal>. As a practical example,
+			you may use <literal>${PJSIP_RESPONSE_HEADERS(X-)}</literal> to enumerate optional
+			extended headers.</para>
+		</description>
+		<see-also>
+			<ref type="function">PJSIP_RESPONSE_HEADER</ref>
+			<ref type="function">PJSIP_HEADERS</ref>
+		</see-also>
+	</function>
+	<function name="PJSIP_HEADER_PARAM" language="en_US">
+		<synopsis>
+			Get or set header/URI parameters on a PJSIP channel.
+		</synopsis>
+		<syntax>
+			<parameter name="header_name" required="true">
+				<para>Header in which parameter should be read or set.</para>
+				<para>Currently, the only supported header is <literal>From</literal>.</para>
+			</parameter>
+			<parameter name="parameter_type" required="true">
+				<para>The type of parameter to get or set.</para>
+				<para>Default is header parameter.</para>
+				<enumlist>
+					<enum name="header">
+						<para>Header parameter.</para>
+					</enum>
+					<enum name="uri">
+						<para>URI parameter.</para>
+					</enum>
+				</enumlist>
+			</parameter>
+			<parameter name="parameter_name" required="true">
+				<para>Name of parameter.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>PJSIP_HEADER_PARAM allows you to read or set parameters in a SIP header on a
+			PJSIP channel.</para>
+			<para>Both URI parameters and header parameters can be read and set using
+			this function. URI parameters appear in the URI (inside the &lt;&gt; in the header)
+			while header parameters appear afterwards.</para>
+			<note><para>If you call PJSIP_HEADER_PARAM in a normal dialplan context you'll be
+			operating on the <emphasis>caller's (incoming)</emphasis> channel which
+			may not be what you want. To operate on the <emphasis>callee's (outgoing)</emphasis>
+			channel call PJSIP_HEADER_PARAM in a pre-dial handler. </para></note>
+			<example title="Set URI parameter in From header on outbound channel">
+			[handler]
+			exten => addheader,1,Set(PJSIP_HEADER_PARAM(From,uri,isup-oli)=27)
+			same => n,Return()
+			[somecontext]
+			exten => 1,1,Dial(PJSIP/${EXTEN},,b(handler^addheader^1))
+			</example>
+			<example title="Read URI parameter in From header on inbound channel">
+			same => n,Set(value=${PJSIP_HEADER_PARAM(From,uri,isup-oli)})
+			</example>
+		</description>
+	</function>
  ***/
 
 /*! \brief Linked list for accumulating headers */
@@ -181,6 +311,10 @@ AST_LIST_HEAD_NOLOCK(hdr_list, hdr_list_entry);
 static const struct ast_datastore_info header_datastore = {
 	.type = "header_datastore",
 };
+/*! \brief Datastore for saving response headers */
+static const struct ast_datastore_info response_header_datastore = {
+	.type = "response_header_datastore",
+};
 
 /*! \brief Data structure used for ast_sip_push_task_wait_serializer  */
 struct header_data {
@@ -190,6 +324,7 @@ struct header_data {
 	char *buf;
 	int header_number;
 	size_t len;
+	const struct ast_datastore_info *header_datastore;
 };
 
 /*!
@@ -244,6 +379,42 @@ static int incoming_request(struct ast_sip_session *session, pjsip_rx_data * rda
 	return 0;
 }
 
+/*!
+ * \internal
+ * \brief Session supplement callback on an incoming INVITE response
+ *
+ * Retrieve the response_header_datastore from the session or create one if it doesn't exist.
+ * Create and initialize the list if needed.
+ * Insert the headers.
+ */
+static void incoming_response(struct ast_sip_session *session, pjsip_rx_data * rdata)
+{
+	pj_pool_t *pool = session->inv_session->dlg->pool;
+	RAII_VAR(struct ast_datastore *, datastore,
+			 ast_sip_session_get_datastore(session, response_header_datastore.type), ao2_cleanup);
+	pjsip_status_line status = rdata->msg_info.msg->line.status;
+
+	/* Skip responses different of 200 OK, when 2xx is received. */
+	if (session->inv_session->state != PJSIP_INV_STATE_CONNECTING || status.code!=200) {
+		return;
+	}
+
+	if (!datastore) {
+		if (!(datastore =
+			  ast_sip_session_alloc_datastore(&response_header_datastore, response_header_datastore.type))
+			||
+			!(datastore->data = pj_pool_alloc(pool, sizeof(struct hdr_list))) ||
+			ast_sip_session_add_datastore(session, datastore)) {
+			ast_log(AST_LOG_ERROR, "Unable to create datastore for header functions.\n");
+			return;
+		}
+		AST_LIST_HEAD_INIT_NOLOCK((struct hdr_list *) datastore->data);
+	}
+	insert_headers(pool, (struct hdr_list *) datastore->data, rdata->msg_info.msg);
+
+	return;
+}
+
 /*!
  * \internal
  * \brief Search list for nth occurrence of specific header.
@@ -271,7 +442,7 @@ static pjsip_hdr *find_header(struct hdr_list *list, const char *header_name,
 
 /*!
  * \internal
- * \brief Implements PJSIP_HEADERS by searching for the requested header prefix.
+ * \brief Implements PJSIP_HEADERS/PJSIP_RESPONSE_HEADERS by searching for the requested header prefix.
  *
  * Retrieve the header_datastore.
  * Search for the all matching headers.
@@ -293,7 +464,7 @@ static int read_headers(void *obj)
 	struct hdr_list *list;
 
 	RAII_VAR(struct ast_datastore *, datastore,
-			 ast_sip_session_get_datastore(data->channel->session, header_datastore.type),
+			 ast_sip_session_get_datastore(data->channel->session, data->header_datastore->type),
 			 ao2_cleanup);
 
 	if (!datastore || !datastore->data) {
@@ -355,10 +526,9 @@ static int read_headers(void *obj)
 	return 0;
 }
 
-
 /*!
  * \internal
- * \brief Implements PJSIP_HEADER 'read' by searching the for the requested header.
+ * \brief Implements PJSIP_HEADER/PJSIP_RESPONSE_HEADER 'read' by searching the for the requested header.
  *
  * Retrieve the header_datastore.
  * Search for the nth matching header.
@@ -379,7 +549,7 @@ static int read_header(void *obj)
 	struct hdr_list *list;
 	int i = 1;
 	RAII_VAR(struct ast_datastore *, datastore,
-			 ast_sip_session_get_datastore(data->channel->session, header_datastore.type),
+			 ast_sip_session_get_datastore(data->channel->session, data->header_datastore->type),
 			 ao2_cleanup);
 
 	if (!datastore || !datastore->data) {
@@ -460,11 +630,11 @@ static int add_header(void *obj)
 	struct hdr_list *list;
 
 	RAII_VAR(struct ast_datastore *, datastore,
-			 ast_sip_session_get_datastore(session, header_datastore.type), ao2_cleanup);
+			 ast_sip_session_get_datastore(session, data->header_datastore->type), ao2_cleanup);
 
 	if (!datastore) {
-		if (!(datastore = ast_sip_session_alloc_datastore(&header_datastore,
-														  header_datastore.type))
+		if (!(datastore = ast_sip_session_alloc_datastore(data->header_datastore,
+														data->header_datastore->type))
 			|| !(datastore->data = pj_pool_alloc(pool, sizeof(struct hdr_list)))
 			|| ast_sip_session_add_datastore(session, datastore)) {
 			ast_log(AST_LOG_ERROR, "Unable to create datastore for header functions.\n");
@@ -503,7 +673,7 @@ static int update_header(void *obj)
 	struct header_data *data = obj;
 	pjsip_hdr *hdr = NULL;
 	RAII_VAR(struct ast_datastore *, datastore,
-			 ast_sip_session_get_datastore(data->channel->session, header_datastore.type),
+			 ast_sip_session_get_datastore(data->channel->session, data->header_datastore->type),
 			 ao2_cleanup);
 
 	if (!datastore || !datastore->data) {
@@ -540,7 +710,7 @@ static int remove_header(void *obj)
 	struct hdr_list_entry *le;
 	int removed_count = 0;
 	RAII_VAR(struct ast_datastore *, datastore,
-			 ast_sip_session_get_datastore(data->channel->session, header_datastore.type),
+			 ast_sip_session_get_datastore(data->channel->session, data->header_datastore->type),
 			 ao2_cleanup);
 
 	if (!datastore || !datastore->data) {
@@ -598,13 +768,47 @@ static int func_read_headers(struct ast_channel *chan, const char *function, cha
 	header_data.header_value = NULL;
 	header_data.buf = buf;
 	header_data.len = len;
+	header_data.header_datastore = &header_datastore;
 
 	return ast_sip_push_task_wait_serializer(channel->session->serializer, read_headers, &header_data);
 
 }
 
 /*!
- * \brief Implements function 'read' callback.
+ * \brief Read list of unique SIP response headers
+ */
+static int func_response_read_headers(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len)
+{
+	struct ast_sip_channel_pvt *channel = chan ? ast_channel_tech_pvt(chan) : NULL;
+	struct header_data header_data;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(header_pattern);
+	);
+	AST_STANDARD_APP_ARGS(args, data);
+
+	if (!chan || strncmp(ast_channel_name(chan), "PJSIP/", 6)) {
+		ast_log(LOG_ERROR, "This function requires a PJSIP channel.\n");
+		return -1;
+	}
+
+	if (ast_strlen_zero(args.header_pattern)) {
+		ast_log(AST_LOG_ERROR, "This function requires a pattern.\n");
+		return -1;
+	}
+
+	header_data.channel = channel;
+	header_data.header_name = args.header_pattern;
+	header_data.header_value = NULL;
+	header_data.buf = buf;
+	header_data.len = len;
+	header_data.header_datastore = &response_header_datastore;
+
+	return ast_sip_push_task_wait_serializer(channel->session->serializer, read_headers, &header_data);
+
+}
+
+/*!
+ * \brief Implements PJSIP_HEADER function 'read' callback.
  *
  * Valid actions are 'read' and 'remove'.
  */
@@ -646,6 +850,7 @@ static int func_read_header(struct ast_channel *chan, const char *function, char
 	header_data.header_value = NULL;
 	header_data.buf = buf;
 	header_data.len = len;
+	header_data.header_datastore = &header_datastore;
 
 	if (!strcasecmp(args.action, "read")) {
 		int res = ast_sip_push_task_wait_serializer(channel->session->serializer,
@@ -664,7 +869,62 @@ static int func_read_header(struct ast_channel *chan, const char *function, char
 }
 
 /*!
- * \brief Implements function 'write' callback.
+ * \brief Implements PJSIP_RESPONSE_HEADER function 'read' callback.
+ *
+ * Valid actions are 'read'
+ */
+static int func_response_read_header(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len)
+{
+	struct ast_sip_channel_pvt *channel = chan ? ast_channel_tech_pvt(chan) : NULL;
+	struct header_data header_data;
+	int number;
+	AST_DECLARE_APP_ARGS(args,
+						 AST_APP_ARG(action);
+						 AST_APP_ARG(header_name); AST_APP_ARG(header_number););
+	AST_STANDARD_APP_ARGS(args, data);
+
+	if (!channel || strncmp(ast_channel_name(chan), "PJSIP/", 6)) {
+		ast_log(LOG_ERROR, "This function requires a PJSIP channel.\n");
+		return -1;
+	}
+
+	if (ast_strlen_zero(args.action)) {
+		ast_log(AST_LOG_ERROR, "This function requires an action.\n");
+		return -1;
+	}
+	if (ast_strlen_zero(args.header_name)) {
+		ast_log(AST_LOG_ERROR, "This function requires a header name.\n");
+		return -1;
+	}
+	if (!args.header_number) {
+		number = 1;
+	} else {
+		sscanf(args.header_number, "%30d", &number);
+		if (number < 1) {
+			number = 1;
+		}
+	}
+
+	header_data.channel = channel;
+	header_data.header_name = args.header_name;
+	header_data.header_number = number;
+	header_data.header_value = NULL;
+	header_data.buf = buf;
+	header_data.len = len;
+	header_data.header_datastore = &response_header_datastore;
+
+	if (!strcasecmp(args.action, "read")) {
+		return ast_sip_push_task_wait_serializer(channel->session->serializer, read_header, &header_data);
+	} else {
+		ast_log(AST_LOG_ERROR,
+				"Unknown action '%s' is not valid, must be 'read'.\n",
+				args.action);
+		return -1;
+	}
+}
+
+/*!
+ * \brief Implements PJSIP_HEADER function 'write' callback.
  *
  * Valid actions are 'add', 'update' and 'remove'.
  */
@@ -707,6 +967,7 @@ static int func_write_header(struct ast_channel *chan, const char *cmd, char *da
 	header_data.header_value = value;
 	header_data.buf = NULL;
 	header_data.len = 0;
+	header_data.header_datastore = &header_datastore;
 
 	if (!strcasecmp(args.action, "add")) {
 		return ast_sip_push_task_wait_serializer(channel->session->serializer,
@@ -736,6 +997,16 @@ static struct ast_custom_function pjsip_headers_function = {
 	.read = func_read_headers
 };
 
+static struct ast_custom_function pjsip_response_header_function = {
+	.name = "PJSIP_RESPONSE_HEADER",
+	.read = func_response_read_header
+};
+
+static struct ast_custom_function pjsip_response_headers_function = {
+	.name = "PJSIP_RESPONSE_HEADERS",
+	.read = func_response_read_headers
+};
+
 /*!
  * \internal
  * \brief Session supplement callback for outgoing INVITE requests
@@ -750,7 +1021,6 @@ static struct ast_custom_function pjsip_headers_function = {
  */
 static void outgoing_request(struct ast_sip_session *session, pjsip_tx_data * tdata)
 {
-	pj_pool_t *pool = session->inv_session->dlg->pool;
 	struct hdr_list *list;
 	struct hdr_list_entry *le;
 	RAII_VAR(struct ast_datastore *, datastore,
@@ -763,7 +1033,7 @@ static void outgoing_request(struct ast_sip_session *session, pjsip_tx_data * td
 
 	list = datastore->data;
 	AST_LIST_TRAVERSE(list, le, nextptr) {
-		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) pjsip_hdr_clone(pool, le->hdr));
+		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) pjsip_hdr_clone(tdata->pool, le->hdr));
 	}
 	ast_sip_session_remove_datastore(session, datastore->uid);
 }
@@ -773,6 +1043,224 @@ static struct ast_sip_session_supplement header_funcs_supplement = {
 	.priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL - 1000,
 	.incoming_request = incoming_request,
 	.outgoing_request = outgoing_request,
+	.incoming_response = incoming_response,
+};
+
+enum param_type {
+	PARAMETER_HEADER,
+	PARAMETER_URI,
+};
+
+struct param_data {
+	struct ast_sip_channel_pvt *channel;
+	char *header_name;
+	char *param_name;
+	const char *param_value; /* Only used for write */
+	enum param_type paramtype;
+	/* For read function only */
+	char *buf;
+	size_t len;
+};
+
+static int read_param(void *obj)
+{
+	struct param_data *data = obj;
+	struct ast_sip_session *session = data->channel->session;
+	pj_str_t param_name;
+
+	pjsip_fromto_hdr *dlg_info;
+	pjsip_name_addr *dlg_info_name_addr;
+	pjsip_sip_uri *dlg_info_uri;
+	pjsip_param *param;
+	size_t param_len;
+
+	dlg_info = session->inv_session->dlg->remote.info; /* Remote dialog for incoming */
+	dlg_info_name_addr = (pjsip_name_addr *) dlg_info->uri;
+	dlg_info_uri = pjsip_uri_get_uri(dlg_info_name_addr);
+
+	pj_cstr(&param_name, data->param_name);
+
+	if (data->paramtype == PARAMETER_URI) { /* URI parameter */
+		param = pjsip_param_find(&dlg_info_uri->other_param, &param_name);
+	} else { /* Header parameter */
+		param = pjsip_param_find(&dlg_info->other_param, &param_name);
+	}
+
+	if (!param) {
+		ast_debug(1, "No %s parameter found named %s\n",
+			data->paramtype == PARAMETER_URI ? "URI" : "header", data->param_name);
+		return -1;
+	}
+
+	param_len = pj_strlen(&param->value);
+	if (param_len >= data->len) {
+		ast_log(LOG_ERROR, "Buffer is too small for parameter value (%zu > %zu)\n", param_len, data->len);
+		return -1;
+	}
+
+	ast_debug(2, "Successfully read %s parameter %s (length %zu)\n",
+		data->paramtype == PARAMETER_URI ? "URI" : "header", data->param_name, param_len);
+	ast_copy_string(data->buf, pj_strbuf(&param->value), data->len);
+	data->buf[pj_strlen(&param->value)] = '\0';
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Implements PJSIP_HEADER_PARAM 'add' by adding the specified parameter.
+ * \note Unlike add_header, we can't add parameters in the outgoing_request callback: that's too late.
+ *       That's why we do it here and not in a callback.
+ */
+static int add_param(void *obj)
+{
+	struct param_data *data = obj;
+	struct ast_sip_session *session = data->channel->session;
+	pj_pool_t *pool = session->inv_session->dlg->pool;
+
+	pjsip_fromto_hdr *dlg_info;
+	pjsip_name_addr *dlg_info_name_addr;
+	pjsip_sip_uri *dlg_info_uri;
+
+	dlg_info = session->inv_session->dlg->local.info; /* Local for outgoing */
+	dlg_info_name_addr = (pjsip_name_addr *) dlg_info->uri;
+	dlg_info_uri = pjsip_uri_get_uri(dlg_info_name_addr);
+	if (!PJSIP_URI_SCHEME_IS_SIP(dlg_info_uri) && !PJSIP_URI_SCHEME_IS_SIPS(dlg_info_uri)) {
+		ast_log(LOG_WARNING, "Non SIP/SIPS URI\n");
+		return -1;
+	}
+
+	ast_debug(1, "Adding custom %s param %s = %s\n",
+		data->paramtype == PARAMETER_URI ? "URI" : "header", data->param_name, data->param_value);
+
+	/* This works the same as doing this in set_from_header in res_pjsip_session.c
+	 * The way that this maps to pjproject is a little confusing.
+	 * Say we have <sip:foo@bar.com;p1=abc;p2=def?h1=qrs&h2=tuv>;o1=foo;o2=bar
+	 * p1 and p2 are URI parameters.
+	 * (h1 and h2 are URI headers)
+	 * o1 and o2 are header parameters (and don't have anything to do with the URI)
+	 * In pjproject, other_param is used for adding all custom parameters.
+	 * We use the URI for URI stuff, including URI parameters, and the header directly for header parameters.
+	 */
+
+#define param_add(pool, list, pname, pvalue) { \
+	pjsip_param *param; \
+	param = PJ_POOL_ALLOC_T(pool, pjsip_param); \
+	pj_strdup2(pool, &param->name, pname); \
+	pj_strdup2(pool, &param->value, pvalue); \
+	pj_list_insert_before(list, param); \
+}
+
+	if (data->paramtype == PARAMETER_URI) { /* URI parameter */
+		param_add(pool, &dlg_info_uri->other_param, data->param_name, S_OR(data->param_value, ""));
+	} else { /* Header parameter */
+		param_add(pool, &dlg_info->other_param, data->param_name, S_OR(data->param_value, ""));
+	}
+
+	return 0;
+}
+
+static int func_read_param(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len)
+{
+	struct ast_sip_channel_pvt *channel = chan ? ast_channel_tech_pvt(chan) : NULL;
+	struct param_data param_data;
+
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(header_name);
+		AST_APP_ARG(param_type);
+		AST_APP_ARG(param_name);
+	);
+
+	AST_STANDARD_APP_ARGS(args, data);
+
+	param_data.channel = channel;
+
+	if (!channel || strncmp(ast_channel_name(chan), "PJSIP/", 6)) {
+		ast_log(LOG_ERROR, "This function requires a PJSIP channel.\n");
+		return -1;
+	}
+	if (ast_strlen_zero(args.param_type)) {
+		ast_log(AST_LOG_ERROR, "This function requires a parameter type.\n");
+		return -1;
+	}
+	if (ast_strlen_zero(args.param_name)) {
+		ast_log(AST_LOG_ERROR, "This function requires a parameter name.\n");
+		return -1;
+	}
+
+	/* Currently, only From is supported, but this could be extended in the future. */
+	if (ast_strlen_zero(args.header_name) || strcasecmp(args.header_name, "From")) {
+		ast_log(LOG_WARNING, "Only the From header is currently supported\n");
+		return -1;
+	}
+
+	param_data.param_name = args.param_name;
+	if (!strcasecmp(args.param_type, "header")) {
+		param_data.paramtype = PARAMETER_HEADER;
+	} else if (!strcasecmp(args.param_type, "uri")) {
+		param_data.paramtype = PARAMETER_URI;
+	} else {
+		ast_log(LOG_WARNING, "Parameter type '%s' is invalid: must be 'header' or 'uri'\n", args.param_type);
+		return -1;
+	}
+
+	param_data.buf = buf;
+	param_data.len = len;
+
+	return ast_sip_push_task_wait_serializer(channel->session->serializer, read_param, &param_data);
+}
+
+static int func_write_param(struct ast_channel *chan, const char *cmd, char *data, const char *value)
+{
+	struct ast_sip_channel_pvt *channel = chan ? ast_channel_tech_pvt(chan) : NULL;
+	struct param_data param_data;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(header_name);
+		AST_APP_ARG(param_type);
+		AST_APP_ARG(param_name);
+	);
+
+	AST_STANDARD_APP_ARGS(args, data);
+
+	param_data.channel = channel;
+
+	if (!channel || strncmp(ast_channel_name(chan), "PJSIP/", 6)) {
+		ast_log(LOG_ERROR, "This function requires a PJSIP channel.\n");
+		return -1;
+	}
+	if (ast_strlen_zero(args.param_type)) {
+		ast_log(AST_LOG_ERROR, "This function requires a parameter type.\n");
+		return -1;
+	}
+	if (ast_strlen_zero(args.param_name)) {
+		ast_log(AST_LOG_ERROR, "This function requires a parameter name.\n");
+		return -1;
+	}
+
+	/* Currently, only From is supported, but this could be extended in the future. */
+	if (ast_strlen_zero(args.header_name) || strcasecmp(args.header_name, "From")) {
+		ast_log(LOG_WARNING, "Only the From header is currently supported\n");
+		return -1;
+	}
+
+	param_data.param_name = args.param_name;
+	if (!strcasecmp(args.param_type, "header")) {
+		param_data.paramtype = PARAMETER_HEADER;
+	} else if (!strcasecmp(args.param_type, "uri")) {
+		param_data.paramtype = PARAMETER_URI;
+	} else {
+		ast_log(LOG_WARNING, "Parameter type '%s' is invalid: must be 'header' or 'uri'\n", args.param_type);
+		return -1;
+	}
+	param_data.param_value = value;
+
+	return ast_sip_push_task_wait_serializer(channel->session->serializer, add_param, &param_data);
+}
+
+static struct ast_custom_function pjsip_header_param_function = {
+	.name = "PJSIP_HEADER_PARAM",
+	.read = func_read_param,
+	.write = func_write_param,
 };
 
 static int load_module(void)
@@ -780,6 +1268,9 @@ static int load_module(void)
 	ast_sip_session_register_supplement(&header_funcs_supplement);
 	ast_custom_function_register(&pjsip_header_function);
 	ast_custom_function_register(&pjsip_headers_function);
+	ast_custom_function_register(&pjsip_response_header_function);
+	ast_custom_function_register(&pjsip_response_headers_function);
+	ast_custom_function_register(&pjsip_header_param_function);
 
 	return AST_MODULE_LOAD_SUCCESS;
 }
@@ -788,6 +1279,9 @@ static int unload_module(void)
 {
 	ast_custom_function_unregister(&pjsip_header_function);
 	ast_custom_function_unregister(&pjsip_headers_function);
+	ast_custom_function_unregister(&pjsip_response_header_function);
+	ast_custom_function_unregister(&pjsip_response_headers_function);
+	ast_custom_function_unregister(&pjsip_header_param_function);
 	ast_sip_session_unregister_supplement(&header_funcs_supplement);
 	return 0;
 }
diff --git a/res/res_pjsip_history.c b/res/res_pjsip_history.c
index de1063b9d6179794dff7fab4470954caeaa87149..9da0af4f33181c12995fbed084039c2490965eff 100644
--- a/res/res_pjsip_history.c
+++ b/res/res_pjsip_history.c
@@ -199,7 +199,7 @@ static int evaluate_equal(struct operator *op, enum aco_option_type type, void *
 	{
 		struct timeval right = { 0, };
 
-		if (sscanf(op_right->field, "%ld", &right.tv_sec) != 1) {
+		if ((right.tv_sec = ast_string_to_time_t(op_right->field)) == -1) {
 			ast_log(LOG_WARNING, "Unable to extract field '%s': not a timestamp\n", op_right->field);
 			return -1;
 		}
@@ -270,7 +270,7 @@ static int evaluate_less_than(struct operator *op, enum aco_option_type type, vo
 	{
 		struct timeval right = { 0, };
 
-		if (sscanf(op_right->field, "%ld", &right.tv_sec) != 1) {
+		if ((right.tv_sec = ast_string_to_time_t(op_right->field)) == -1) {
 			ast_log(LOG_WARNING, "Unable to extract field '%s': not a timestamp\n", op_right->field);
 			return -1;
 		}
@@ -319,7 +319,7 @@ static int evaluate_greater_than(struct operator *op, enum aco_option_type type,
 	{
 		struct timeval right = { 0, };
 
-		if (sscanf(op_right->field, "%ld", &right.tv_sec) != 1) {
+		if ((right.tv_sec = ast_string_to_time_t(op_right->field)) == -1) {
 			ast_log(LOG_WARNING, "Unable to extract field '%s': not a timestamp\n", op_right->field);
 			return -1;
 		}
@@ -656,7 +656,7 @@ static struct pjsip_history_entry *pjsip_history_entry_alloc(pjsip_msg *msg)
 /*! \brief Format single line history entry */
 static void sprint_list_entry(struct pjsip_history_entry *entry, char *line, int len)
 {
-	char addr[64];
+	char addr[64], secs[AST_TIME_T_LEN];
 
 	if (entry->transmitted) {
 		pj_sockaddr_print(&entry->dst, addr, sizeof(addr), 3);
@@ -664,22 +664,24 @@ static void sprint_list_entry(struct pjsip_history_entry *entry, char *line, int
 		pj_sockaddr_print(&entry->src, addr, sizeof(addr), 3);
 	}
 
+	ast_time_t_to_string(entry->timestamp.tv_sec, secs, sizeof(secs));
+
 	if (entry->msg->type == PJSIP_REQUEST_MSG) {
 		char uri[128];
 
 		pjsip_uri_print(PJSIP_URI_IN_REQ_URI, entry->msg->line.req.uri, uri, sizeof(uri));
-		snprintf(line, len, "%-5.5d %-10.10ld %-5.5s %-24.24s %.*s %s SIP/2.0",
+		snprintf(line, len, "%-5.5d %-10.10s %-5.5s %-24.24s %.*s %s SIP/2.0",
 			entry->number,
-			entry->timestamp.tv_sec,
+			secs,
 			entry->transmitted ? "* ==>" : "* <==",
 			addr,
 			(int)pj_strlen(&entry->msg->line.req.method.name),
 			pj_strbuf(&entry->msg->line.req.method.name),
 			uri);
 	} else {
-		snprintf(line, len, "%-5.5d %-10.10ld %-5.5s %-24.24s SIP/2.0 %u %.*s",
+		snprintf(line, len, "%-5.5d %-10.10s %-5.5s %-24.24s SIP/2.0 %u %.*s",
 			entry->number,
-			entry->timestamp.tv_sec,
+			secs,
 			entry->transmitted ? "* ==>" : "* <==",
 			addr,
 			entry->msg->line.status.code,
@@ -1149,7 +1151,7 @@ static struct vector_history_t *filter_history(struct ast_cli_args *a)
 /*! \brief Print a detailed view of a single entry in the history to the CLI */
 static void display_single_entry(struct ast_cli_args *a, struct pjsip_history_entry *entry)
 {
-	char addr[64];
+	char addr[64], secs[AST_TIME_T_LEN];
 	char *buf;
 
 	buf = ast_calloc(1, PJSIP_MAX_PKT_LEN * sizeof(char));
@@ -1169,11 +1171,12 @@ static void display_single_entry(struct ast_cli_args *a, struct pjsip_history_en
 		pj_sockaddr_print(&entry->src, addr, sizeof(addr), 3);
 	}
 
-	ast_cli(a->fd, "<--- History Entry %d %s %s at %-10.10ld --->\n",
+	ast_time_t_to_string(entry->timestamp.tv_sec, secs, sizeof(secs));
+	ast_cli(a->fd, "<--- History Entry %d %s %s at %-10.10s --->\n",
 		entry->number,
 		entry->transmitted ? "Sent to" : "Received from",
 		addr,
-		entry->timestamp.tv_sec);
+		secs);
 	ast_cli(a->fd, "%s\n", buf);
 
 	ast_free(buf);
diff --git a/res/res_pjsip_logger.c b/res/res_pjsip_logger.c
index 957020f8a0027c1ede42a2d29233a02b1f4638f4..456bb224a94b48687a184b8627bdf34bb111d028 100644
--- a/res/res_pjsip_logger.c
+++ b/res/res_pjsip_logger.c
@@ -30,6 +30,7 @@
 #include <pjsip.h>
 
 #include "asterisk/res_pjsip.h"
+#include "asterisk/vector.h"
 #include "asterisk/module.h"
 #include "asterisk/logger.h"
 #include "asterisk/cli.h"
@@ -99,6 +100,12 @@ struct pcap_udp_header {
 	uint16_t checksum;	/*! \brief Packet checksum, left uncalculated for our purposes */
 };
 
+struct method_logging_info {
+	pj_str_t pj_name;    /*! \brief A PJSIP string for the method */
+	pjsip_method method; /*! \brief The PJSIP method structure used for comparisons */
+	char name[];         /*! \brief The method name */
+};
+
 /*! \brief PJSIP Logging Session */
 struct pjsip_logger_session {
 	/*! \brief Explicit addresses or ranges being logged */
@@ -115,6 +122,8 @@ struct pjsip_logger_session {
 	unsigned int log_to_verbose:1;
 	/*! \brief Whether to log to pcap or not */
 	unsigned int log_to_pcap:1;
+	/*! \brief Vector of SIP methods to log */
+	AST_VECTOR(, struct method_logging_info *) log_methods;
 };
 
 /*! \brief The default logger session */
@@ -130,6 +139,9 @@ static void pjsip_logger_session_destroy(void *obj)
 	}
 
 	ast_free_ha(session->matches);
+
+	AST_VECTOR_RESET(&session->log_methods, ast_free);
+	AST_VECTOR_FREE(&session->log_methods);
 }
 
 /*! \brief Allocator for logger session */
@@ -145,11 +157,35 @@ static struct pjsip_logger_session *pjsip_logger_session_alloc(void)
 
 	session->log_to_verbose = 1;
 
+	AST_VECTOR_INIT(&session->log_methods, 0);
+
 	return session;
 }
 
-/*! \brief See if we pass debug IP filter */
-static inline int pjsip_log_test_addr(const struct pjsip_logger_session *session, const char *address, int port)
+/*! \note Must be called with the pjsip_logger_session lock held */
+static int apply_method_filter(const struct pjsip_logger_session *session, const pjsip_method *method)
+{
+	size_t size = AST_VECTOR_SIZE(&session->log_methods);
+	size_t i;
+
+	if (size == 0) {
+		/* Nothing in the vector means everything matches */
+		return 0;
+	}
+
+	for (i = 0; i < size; ++i) {
+		struct method_logging_info *candidate = AST_VECTOR_GET(&session->log_methods, i);
+		if (pjsip_method_cmp(&candidate->method, method) == 0) {
+			return 0;
+		}
+	}
+
+	/* Nothing matched */
+	return 1;
+}
+
+/*! \brief See if we pass debug filter */
+static inline int pjsip_log_test_filter(const struct pjsip_logger_session *session, const char *address, int port, const pjsip_method *method)
 {
 	struct ast_sockaddr test_addr;
 
@@ -161,9 +197,15 @@ static inline int pjsip_log_test_addr(const struct pjsip_logger_session *session
 		return 1;
 	}
 
+	if (apply_method_filter(session, method)) {
+		/* The method filter didn't match anything, so reject. */
+		return 0;
+	}
+
 	/* A null address was passed in or no explicit matches. Just reject it. */
 	if (ast_strlen_zero(address) || !session->matches) {
-		return 0;
+		/* If we matched on method and host is empty, accept, otherwise reject. */
+		return AST_VECTOR_SIZE(&session->log_methods) > 0;
 	}
 
 	ast_sockaddr_parse(&test_addr, address, PARSE_PORT_IGNORE);
@@ -270,7 +312,7 @@ static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata)
 	char buffer[AST_SOCKADDR_BUFLEN];
 
 	ao2_rdlock(default_logger);
-	if (!pjsip_log_test_addr(default_logger, tdata->tp_info.dst_name, tdata->tp_info.dst_port)) {
+	if (!pjsip_log_test_filter(default_logger, tdata->tp_info.dst_name, tdata->tp_info.dst_port, &tdata->msg->line.req.method)) {
 		ao2_unlock(default_logger);
 		return PJ_SUCCESS;
 	}
@@ -302,7 +344,7 @@ static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata)
 	}
 
 	ao2_rdlock(default_logger);
-	if (!pjsip_log_test_addr(default_logger, rdata->pkt_info.src_name, rdata->pkt_info.src_port)) {
+	if (!pjsip_log_test_filter(default_logger, rdata->pkt_info.src_name, rdata->pkt_info.src_port, &rdata->msg_info.msg->line.req.method)) {
 		ao2_unlock(default_logger);
 		return PJ_FALSE;
 	}
@@ -393,6 +435,93 @@ static char *pjsip_enable_logger_host(int fd, const char *arg, unsigned int add_
 	return CLI_SUCCESS;
 }
 
+static struct method_logging_info *method_logging_info_alloc(const char *method)
+{
+	size_t method_bytes = strlen(method);
+	struct method_logging_info *info;
+
+	info = ast_calloc(1, sizeof(struct method_logging_info) + method_bytes + 1);
+	if (!info) {
+		return NULL;
+	}
+
+	memcpy(info->name, method, method_bytes + 1);
+	pj_strset(&info->pj_name, info->name, method_bytes);
+	pjsip_method_init_np(&info->method, &info->pj_name);
+
+	return info;
+}
+
+static int method_logging_info_cmp(const struct method_logging_info *element,
+	const struct method_logging_info *candidate)
+{
+	return pjsip_method_cmp(&element->method, &candidate->method) == 0
+		? CMP_MATCH | CMP_STOP
+		: 0;
+}
+
+static int method_logging_info_sort_cmp(const void *a, const void *b)
+{
+	const struct method_logging_info *const *m_a = a;
+	const struct method_logging_info *const *m_b = b;
+	return strcasecmp((*m_a)->name, (*m_b)->name);
+}
+
+/*! \brief Add the current or an additional method to match for filtering */
+static char *pjsip_enable_logger_method(int fd, const char *arg, int add_method)
+{
+	struct ast_str *str;
+	struct method_logging_info *method;
+
+	method = method_logging_info_alloc(arg);
+	if (!method) {
+		return CLI_FAILURE;
+	}
+
+	ao2_wrlock(default_logger);
+	default_logger->enabled = 1;
+
+	if (!add_method) {
+		/* Remove what already exists */
+		AST_VECTOR_RESET(&default_logger->log_methods, ast_free);
+	}
+
+	/* Already in the list? */
+	if (AST_VECTOR_CALLBACK(&default_logger->log_methods, method_logging_info_cmp, NULL, method) != NULL) {
+		ast_cli(fd, "Method '%s' is already enabled\n", method->name);
+		ao2_unlock(default_logger);
+		ast_free(method);
+		return CLI_SUCCESS;
+	}
+
+	if (AST_VECTOR_APPEND(&default_logger->log_methods, method)) {
+		ast_log(LOG_ERROR, "Cannot register logger method '%s'. Unable to append.\n", method->name);
+		ao2_unlock(default_logger);
+		ast_free(method);
+		return CLI_SUCCESS;
+	}
+
+	AST_VECTOR_SORT(&default_logger->log_methods, method_logging_info_sort_cmp);
+
+	str = ast_str_create(256);
+	if (str) {
+		size_t i;
+		for (i = 0; i < AST_VECTOR_SIZE(&default_logger->log_methods); i++) {
+			method = AST_VECTOR_GET(&default_logger->log_methods, i);
+			ast_str_append(&str, 0, "%s%.*s",
+				ast_str_strlen(str) ? ", " : "",
+				(int) method->pj_name.slen, method->pj_name.ptr);
+		}
+
+		ast_cli(fd, "PJSIP Logging Enabled for SIP Methods: %s\n", ast_str_buffer(str));
+		ast_free(str);
+	}
+
+	ao2_unlock(default_logger);
+
+	return CLI_SUCCESS;
+}
+
 static char *pjsip_disable_logger(int fd)
 {
 	ao2_wrlock(default_logger);
@@ -404,6 +533,8 @@ static char *pjsip_disable_logger(int fd)
 	default_logger->log_to_verbose = 1;
 	default_logger->log_to_pcap = 0;
 
+	AST_VECTOR_RESET(&default_logger->log_methods, ast_free);
+
 	/* Stop logging to the PCAP file if active */
 	if (default_logger->pcap_file) {
 		fclose(default_logger->pcap_file);
@@ -469,18 +600,32 @@ static char *pjsip_set_logger_pcap(int fd, const char *arg)
 
 static char *pjsip_set_logger(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
+	static const char * const method_choices[] = {
+		"INVITE", "CANCEL", "ACK",
+		"BYE", "REGISTER", "OPTION",
+		"SUBSCRIBE", "NOTIFY", "PUBLISH",
+		"INFO", "MESSAGE",
+		NULL
+	};
+
 	const char *what;
 
 	if (cmd == CLI_INIT) {
-		e->command = "pjsip set logger {on|off|host|add|verbose|pcap}";
+		e->command = "pjsip set logger {on|off|host|add|method|methodadd|verbose|pcap}";
 		e->usage =
-			"Usage: pjsip set logger {on|off|host <name/subnet>|add <name/subnet>|verbose <on/off>|pcap <filename>}\n"
+			"Usage: pjsip set logger {on|off|host <name/subnet>|add <name/subnet>|method <method>|methodadd <method>|verbose <on/off>|pcap <filename>}\n"
 			"       Enables or disabling logging of SIP packets\n"
 			"       read on ports bound to PJSIP transports either\n"
 			"       globally or enables logging for an individual\n"
-			"       host.\n";
+			"       host or particular SIP method(s).\n"
+			"       Messages can be filtered by SIP request methods\n"
+			"       INVITE, CANCEL, ACK, BYE, REGISTER, OPTION\n"
+			"       SUBSCRIBE, NOTIFY, PUBLISH, INFO, and MESSAGE\n";
 		return NULL;
 	} else if (cmd == CLI_GENERATE) {
+		if (a->argc && !strncasecmp(a->argv[e->args - 1], "method", 6)) {
+			return ast_cli_complete(a->word, method_choices, a->n);
+		}
 		return NULL;
 	}
 
@@ -497,6 +642,10 @@ static char *pjsip_set_logger(struct ast_cli_entry *e, int cmd, struct ast_cli_a
 			return pjsip_enable_logger_host(a->fd, a->argv[e->args], 0);
 		} else if (!strcasecmp(what, "add")) {
 			return pjsip_enable_logger_host(a->fd, a->argv[e->args], 1);
+		} else if (!strcasecmp(what, "method")) {
+			return pjsip_enable_logger_method(a->fd, a->argv[e->args], 0);
+		} else if (!strcasecmp(what, "methodadd")) {
+			return pjsip_enable_logger_method(a->fd, a->argv[e->args], 1);
 		} else if (!strcasecmp(what, "verbose")) {
 			return pjsip_set_logger_verbose(a->fd, a->argv[e->args]);
 		} else if (!strcasecmp(what, "pcap")) {
diff --git a/res/res_pjsip_messaging.c b/res/res_pjsip_messaging.c
index f0db27cec652d5d2ee38e2656e57fabca83b1bb5..336d392cd40cc19bb438ca61f601f6fa99568954 100644
--- a/res/res_pjsip_messaging.c
+++ b/res/res_pjsip_messaging.c
@@ -1055,7 +1055,6 @@ static enum pjsip_status_code rx_data_to_ast_msg(pjsip_rx_data *rdata, struct as
 {
 	RAII_VAR(struct ast_sip_endpoint *, endpt, NULL, ao2_cleanup);
 	pjsip_uri *ruri = rdata->msg_info.msg->line.req.uri;
-	pjsip_sip_uri *sip_ruri;
 	pjsip_name_addr *name_addr;
 	char buf[MAX_BODY_SIZE];
 	const char *field;
@@ -1064,12 +1063,11 @@ static enum pjsip_status_code rx_data_to_ast_msg(pjsip_rx_data *rdata, struct as
 	int res = 0;
 	int size;
 
-	if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) {
+	if (!ast_sip_is_allowed_uri(ruri)) {
 		return PJSIP_SC_UNSUPPORTED_URI_SCHEME;
 	}
 
-	sip_ruri = pjsip_uri_get_uri(ruri);
-	ast_copy_pj_str(exten, &sip_ruri->user, AST_MAX_EXTENSION);
+	ast_copy_pj_str(exten, ast_sip_pjsip_uri_get_username(ruri), AST_MAX_EXTENSION);
 
 	/*
 	 * We may want to match in the dialplan without any user
diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c
index ba684862b33bed140f3965f8e6b445e4773364ca..dfe5481f334a29e88bd80dfb48f025751f14d14c 100644
--- a/res/res_pjsip_mwi.c
+++ b/res/res_pjsip_mwi.c
@@ -1524,6 +1524,7 @@ static int reload(void)
 
 static int unload_module(void)
 {
+#if 0
 	struct ao2_container *unsolicited_mwi;
 
 	ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &global_observer);
@@ -1548,6 +1549,18 @@ static int unload_module(void)
 	ast_free(default_voicemail_extension);
 	default_voicemail_extension = NULL;
 	return 0;
+#else
+	/* If we were allowed to unload, the above is what we would do.
+	 * pjsip_evsub_register_pkg is called by ast_sip_register_subscription_handler
+	 * but there is no corresponding unregister function, so unloading
+	 * a module does not remove the event package. If this module is ever
+	 * loaded again, then pjproject will assert and cause a crash.
+	 * For that reason, we must not be allowed to unload, but if
+	 * a pjsip_evsub_unregister_pkg API is added in the future
+	 * then we should go back to unloading the module as intended.
+	 */
+	return -1;
+#endif
 }
 
 static int load_module(void)
diff --git a/res/res_pjsip_nat.c b/res/res_pjsip_nat.c
index 2d5e6a7583ede5e17917830fae6adb4634980e95..59223f7bbf08f11db80611440757097409448332 100644
--- a/res/res_pjsip_nat.c
+++ b/res/res_pjsip_nat.c
@@ -112,7 +112,6 @@ static void rewrite_uri(pjsip_rx_data *rdata, pjsip_sip_uri *uri, pj_pool_t *poo
  * for the subsequent requests and responses & then be able to properly update
  * the dialog object for all required events.
  */
-
 static int rewrite_route_set(pjsip_rx_data *rdata, pjsip_dialog *dlg)
 {
 	pjsip_rr_hdr *rr = NULL;
diff --git a/res/res_pjsip_notify.c b/res/res_pjsip_notify.c
index 3ae9f625a815c47da914eade84c5e5af797cbeb5..3d88c18a882c3868f3b91a162f10638a6b496fbc 100644
--- a/res/res_pjsip_notify.c
+++ b/res/res_pjsip_notify.c
@@ -52,11 +52,16 @@
 			<parameter name="channel" required="false">
 				<para>Channel name to send the NOTIFY. Must be a PJSIP channel.</para>
 			</parameter>
-			<parameter name="Variable" required="true">
+			<parameter name="Option" required="false">
+				<para>The config section name from <literal>pjsip_notify.conf</literal> to use.</para>
+				<para>One of Option or Variable must be specified.</para>
+			</parameter>
+			<parameter name="Variable" required="false">
 				<para>Appends variables as headers/content to the NOTIFY. If the variable is
 				named <literal>Content</literal>, then the value will compose the body
 				of the message if another variable sets <literal>Content-Type</literal>.
 				<replaceable>name</replaceable>=<replaceable>value</replaceable></para>
+				<para>One of Option or Variable must be specified.</para>
 			</parameter>
 		</syntax>
 		<description>
@@ -1071,6 +1076,47 @@ static struct ast_cli_entry cli_options[] = {
 	AST_CLI_DEFINE(cli_notify, "Send a NOTIFY request to a SIP endpoint")
 };
 
+enum notify_type {
+	NOTIFY_ENDPOINT,
+	NOTIFY_URI,
+	NOTIFY_CHANNEL,
+};
+
+static void manager_send_response(struct mansession *s, const struct message *m, enum notify_type type, enum notify_result res, struct ast_variable *vars, const char *endpoint_name)
+{
+	switch (res) {
+	case INVALID_CHANNEL:
+		if (type == NOTIFY_CHANNEL) {
+			ast_variables_destroy(vars);
+			astman_send_error(s, m, "Channel not found");
+		} else {
+			/* Shouldn't be possible. */
+			ast_assert(0);
+		}
+		break;
+	case INVALID_ENDPOINT:
+		if (type == NOTIFY_ENDPOINT) {
+			ast_variables_destroy(vars);
+			astman_send_error_va(s, m, "Unable to retrieve endpoint %s", endpoint_name);
+		} else {
+			/* Shouldn't be possible. */
+			ast_assert(0);
+		}
+		break;
+	case ALLOC_ERROR:
+		ast_variables_destroy(vars);
+		astman_send_error(s, m, "Unable to allocate NOTIFY task data");
+		break;
+	case TASK_PUSH_ERROR:
+		/* Don't need to destroy vars since it is handled by cleanup in push_notify, push_notify_uri, etc. */
+		astman_send_error(s, m, "Unable to push Notify task");
+		break;
+	case SUCCESS:
+		astman_send_ack(s, m, "NOTIFY sent");
+		break;
+	}
+}
+
 /*!
  * \internal
  * \brief Completes SIPNotify AMI command in Endpoint mode.
@@ -1078,7 +1124,19 @@ static struct ast_cli_entry cli_options[] = {
 static void manager_notify_endpoint(struct mansession *s,
 	const struct message *m, const char *endpoint_name)
 {
-	struct ast_variable *vars = astman_get_variables_order(m, ORDER_NATURAL);
+	RAII_VAR(struct notify_cfg *, cfg, NULL, ao2_cleanup);
+	RAII_VAR(struct notify_option *, option, NULL, ao2_cleanup);
+	struct ast_variable *vars = NULL;
+	enum notify_result res;
+	const char *option_name = astman_get_header(m, "Option");
+
+	if (!ast_strlen_zero(option_name) && (cfg = ao2_global_obj_ref(globals)) && !(option = notify_option_find(cfg->notify_options, option_name))) {
+		astman_send_error_va(s, m, "Unable to find notify type '%s'\n", option_name);
+		return;
+	}
+	if (!option) {
+		vars = astman_get_variables_order(m, ORDER_NATURAL);
+	}
 
 	if (!strncasecmp(endpoint_name, "sip/", 4)) {
 		endpoint_name += 4;
@@ -1088,28 +1146,13 @@ static void manager_notify_endpoint(struct mansession *s,
 		endpoint_name += 6;
 	}
 
-	switch (push_notify(endpoint_name, vars, notify_ami_data_create)) {
-	case INVALID_CHANNEL:
-		/* Shouldn't be possible. */
-		ast_assert(0);
-		break;
-	case INVALID_ENDPOINT:
-		ast_variables_destroy(vars);
-		astman_send_error_va(s, m, "Unable to retrieve endpoint %s",
-			endpoint_name);
-		break;
-	case ALLOC_ERROR:
-		ast_variables_destroy(vars);
-		astman_send_error(s, m, "Unable to allocate NOTIFY task data");
-		break;
-	case TASK_PUSH_ERROR:
-		/* Don't need to destroy vars since it is handled by cleanup in push_notify */
-		astman_send_error(s, m, "Unable to push NOTIFY task");
-		break;
-	case SUCCESS:
-		astman_send_ack(s, m, "NOTIFY sent");
-		break;
+	if (option) {
+		res = push_notify(endpoint_name, option, notify_cli_data_create); /* The CLI version happens to be suitable for options. */
+	} else {
+		res = push_notify(endpoint_name, vars, notify_ami_data_create);
 	}
+
+	manager_send_response(s, m, NOTIFY_ENDPOINT, res, vars, endpoint_name);
 }
 
 /*!
@@ -1119,29 +1162,27 @@ static void manager_notify_endpoint(struct mansession *s,
 static void manager_notify_uri(struct mansession *s,
 	const struct message *m, const char *uri)
 {
-	struct ast_variable *vars = astman_get_variables_order(m, ORDER_NATURAL);
+	RAII_VAR(struct notify_cfg *, cfg, NULL, ao2_cleanup);
+	RAII_VAR(struct notify_option *, option, NULL, ao2_cleanup);
+	enum notify_result res;
+	const char *option_name = astman_get_header(m, "Option");
+	struct ast_variable *vars = NULL;
 
-	switch (push_notify_uri(uri, vars, notify_ami_uri_data_create)) {
-	case INVALID_CHANNEL:
-		/* Shouldn't be possible. */
-		ast_assert(0);
-		break;
-	case INVALID_ENDPOINT:
-		/* Shouldn't be possible. */
-		ast_assert(0);
-		break;
-	case ALLOC_ERROR:
-		ast_variables_destroy(vars);
-		astman_send_error(s, m, "Unable to allocate NOTIFY task data");
-		break;
-	case TASK_PUSH_ERROR:
-		/* Don't need to destroy vars since it is handled by cleanup in push_notify_uri */
-		astman_send_error(s, m, "Unable to push Notify task");
-		break;
-	case SUCCESS:
-		astman_send_ack(s, m, "NOTIFY sent");
-		break;
+	if (!ast_strlen_zero(option_name) && (cfg = ao2_global_obj_ref(globals)) && !(option = notify_option_find(cfg->notify_options, option_name))) {
+		astman_send_error_va(s, m, "Unable to find notify type '%s'\n", option_name);
+		return;
+	}
+	if (!option) {
+		vars = astman_get_variables_order(m, ORDER_NATURAL);
+	}
+
+	if (option) {
+		res = push_notify_uri(uri, option, notify_cli_uri_data_create);
+	} else {
+		res = push_notify_uri(uri, vars, notify_ami_uri_data_create);
 	}
+
+	manager_send_response(s, m, NOTIFY_URI, res, vars, NULL);
 }
 
 /*!
@@ -1151,29 +1192,13 @@ static void manager_notify_uri(struct mansession *s,
 static void manager_notify_channel(struct mansession *s,
 	const struct message *m, const char *channel)
 {
-	struct ast_variable *vars = astman_get_variables_order(m, ORDER_NATURAL);
+	enum notify_result res;
+	struct ast_variable *vars = NULL;
 
-	switch (push_notify_channel(channel, vars, notify_ami_channel_data_create)) {
-	case INVALID_CHANNEL:
-		ast_variables_destroy(vars);
-		astman_send_error(s, m, "Channel not found");
-		break;
-	case INVALID_ENDPOINT:
-		/* Shouldn't be possible. */
-		ast_assert(0);
-		break;
-	case ALLOC_ERROR:
-		ast_variables_destroy(vars);
-		astman_send_error(s, m, "Unable to allocate NOTIFY task data");
-		break;
-	case TASK_PUSH_ERROR:
-		/* Don't need to destroy vars since it is handled by cleanup in push_notify_channel */
-		astman_send_error(s, m, "Unable to push Notify task");
-		break;
-	case SUCCESS:
-		astman_send_ack(s, m, "NOTIFY sent");
-		break;
-	}
+	vars = astman_get_variables_order(m, ORDER_NATURAL);
+	res = push_notify_channel(channel, vars, notify_ami_channel_data_create);
+
+	manager_send_response(s, m, NOTIFY_CHANNEL, res, vars, NULL);
 }
 
 /*!
@@ -1185,6 +1210,8 @@ static int manager_notify(struct mansession *s, const struct message *m)
 	const char *endpoint_name = astman_get_header(m, "Endpoint");
 	const char *uri = astman_get_header(m, "URI");
 	const char *channel = astman_get_header(m, "Channel");
+	const char *variables = astman_get_header(m, "Variable");
+	const char *option = astman_get_header(m, "Option");
 	int count = 0;
 
 	if (!ast_strlen_zero(endpoint_name)) {
@@ -1197,7 +1224,11 @@ static int manager_notify(struct mansession *s, const struct message *m)
 		++count;
 	}
 
-	if (1 < count) {
+	if ((!ast_strlen_zero(option) && !ast_strlen_zero(variables)) || (ast_strlen_zero(option) && ast_strlen_zero(variables))) {
+		astman_send_error(s, m,
+			"PJSIPNotify requires either an Option or Variable(s)."
+			"You must use only one of them.");
+	} else if (1 < count) {
 		astman_send_error(s, m,
 			"PJSIPNotify requires either an endpoint name, a SIP URI, or a channel.  "
 			"You must use only one of them.");
diff --git a/res/res_pjsip_outbound_authenticator_digest.c b/res/res_pjsip_outbound_authenticator_digest.c
index 4821082a9f538eb2b0fc636f09a26894ae046d5b..aee4afc90ec5a23b9b4090ce1c278eb9c5f24380 100644
--- a/res/res_pjsip_outbound_authenticator_digest.c
+++ b/res/res_pjsip_outbound_authenticator_digest.c
@@ -384,9 +384,9 @@ static pj_status_t set_outbound_authentication_credentials(pjsip_auth_clt_sess *
 	res = pjsip_auth_clt_set_credentials(auth_sess, cred_count, creds_array);
 	ast_free(creds_array);
 	if (res == PJ_SUCCESS) {
-		ast_debug(3, "Set %"PRIu64" credentials in auth session\n", cred_count);
+		ast_debug(3, "Set %zu credentials in auth session\n", cred_count);
 	} else {
-		ast_log(LOG_ERROR, "Failed to set %"PRIu64" credentials in auth session\n", cred_count);
+		ast_log(LOG_ERROR, "Failed to set %zu credentials in auth session\n", cred_count);
 	}
 
 cleanup:
diff --git a/res/res_pjsip_outbound_registration.c b/res/res_pjsip_outbound_registration.c
index 73989dd528936c7a816354d9852f30430b2fdd88..7834367c9bc65ad95197690152e62babc76664c0 100644
--- a/res/res_pjsip_outbound_registration.c
+++ b/res/res_pjsip_outbound_registration.c
@@ -41,6 +41,7 @@
 #include "res_pjsip/include/res_pjsip_private.h"
 #include "asterisk/vector.h"
 #include "asterisk/pbx.h"
+
 /*** DOCUMENTATION
 	<configInfo name="res_pjsip_outbound_registration" language="en_US">
 		<synopsis>SIP resource for outbound registrations</synopsis>
@@ -75,8 +76,8 @@
 						on networking specifics and configuration of the registrar.
 					</para></description>
 				</configOption>
-				<configOption name="contact_user">
-					<synopsis>Contact User to use in request</synopsis>
+				<configOption name="contact_user" default="s">
+					<synopsis>Contact User to use in request. If this value is not set, this defaults to 's'</synopsis>
 				</configOption>
 				<configOption name="contact_header_params">
 					<synopsis>Header parameters to place in the Contact header</synopsis>
@@ -92,6 +93,22 @@
 						are made.
 					</para></description>
 				</configOption>
+				<configOption name="security_negotiation" default="no">
+					<synopsis>The kind of security agreement negotiation to use. Currently, only mediasec is supported.</synopsis>
+					<description>
+						<enumlist>
+							<enum name="no" />
+							<enum name="mediasec" />
+						</enumlist>
+					</description>
+				</configOption>
+				<configOption name="security_mechanisms">
+					<synopsis>List of security mechanisms supported.</synopsis>
+					<description><para>
+						This is a comma-delimited list of security mechanisms to use. Each security mechanism
+						must be in the form defined by RFC 3329 section 2.2.
+					</para></description>
+				</configOption>
 				<configOption name="outbound_auth" default="">
 					<synopsis>Authentication object(s) to be used for outbound registrations.</synopsis>
 					<description><para>
@@ -109,6 +126,15 @@
 				<configOption name="outbound_proxy" default="">
 					<synopsis>Full SIP URI of the outbound proxy used to send registrations</synopsis>
 				</configOption>
+				<configOption name="max_random_initial_delay" default="10">
+					<synopsis>Maximum interval in seconds for which an initial registration may be randomly delayed</synopsis>
+					<description>
+						<para>By default, registrations are randomly delayed by a small amount to prevent
+						too many registrations from being made simultaneously.</para>
+						<para>Depending on your system usage, it may be desirable to set this to a smaller
+						or larger value to have fine grained control over the size of this random delay.</para>
+					</description>
+				</configOption>
 				<configOption name="retry_interval" default="1">
 					<synopsis>Interval in seconds between retries if outbound registration is unsuccessful</synopsis>
 				</configOption>
@@ -331,6 +357,8 @@ struct sip_outbound_registration {
 	);
 	/*! \brief Requested expiration time */
 	unsigned int expiration;
+	/*! \brief Maximum random initial delay interval for initial registrations */
+	unsigned int max_random_initial_delay;
 	/*! \brief Interval at which retries should occur for temporal responses */
 	unsigned int retry_interval;
 	/*! \brief Interval at which retries should occur for permanent responses */
@@ -343,6 +371,10 @@ struct sip_outbound_registration {
 	unsigned int max_retries;
 	/*! \brief Whether to add a line parameter to the outbound Contact or not */
 	unsigned int line;
+	/*! \brief Type of security negotiation to use (RFC 3329). */
+	enum ast_sip_security_negotiation security_negotiation;
+	/*! \brief Client security mechanisms (RFC 3329). */
+	struct ast_sip_security_mechanism_vector security_mechanisms;
 	/*! \brief Configured authentication credentials */
 	struct ast_sip_auth_vector outbound_auths;
 	/*! \brief Whether Path support is enabled */
@@ -394,6 +426,12 @@ struct sip_outbound_registration_client_state {
 	unsigned int mediasec;
 	/*! \brief Determines whether SIP Outbound support should be advertised */
 	unsigned int support_outbound;
+	/*! \brief Type of security negotiation to use (RFC 3329). */
+	enum ast_sip_security_negotiation security_negotiation;
+	/*! \brief Client security mechanisms (RFC 3329). */
+	struct ast_sip_security_mechanism_vector security_mechanisms;
+	/*! \brief Security mechanisms of the peer (RFC 3329). */
+	struct ast_sip_security_mechanism_vector server_security_mechanisms;
 	/*! CSeq number of last sent auth request. */
 	unsigned int auth_cseq;
 	/*! \brief Serializer for stuff and things */
@@ -404,12 +442,16 @@ struct sip_outbound_registration_client_state {
 	unsigned int destroy:1;
 	/*! \brief Non-zero if we have attempted sending a REGISTER with authentication */
 	unsigned int auth_attempted:1;
+	/*! \brief Status code of last response if we have tried to register before */
+	int last_status_code;
 	/*! \brief The name of the transport to be used for the registration */
 	char *transport_name;
 	/*! \brief The name of the registration sorcery object */
 	char *registration_name;
 	/*! \brief Indicator, if there was a 494 response before */
 	unsigned int is494;
+	/*! \brief Expected time of registration lapse/expiration */
+	unsigned int registration_expires;
 };
 
 /*! \brief Outbound registration state information (persists for lifetime that registration should exist) */
@@ -516,14 +558,9 @@ static int line_identify_relationship(void *obj, void *arg, int flags)
 
 static struct pjsip_param *get_uri_option_line(const void *uri)
 {
-	pjsip_sip_uri *pjuri;
 	static const pj_str_t LINE_STR = { "line", 4 };
 
-	if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri)) {
-		return NULL;
-	}
-	pjuri = pjsip_uri_get_uri(uri);
-	return pjsip_param_find(&pjuri->other_param, &LINE_STR);
+	return ast_sip_pjsip_uri_get_other_param((pjsip_uri *)uri, &LINE_STR);
 }
 
 /*! \brief Endpoint identifier which uses the 'line' parameter to establish a relationship to an outgoing registration */
@@ -571,6 +608,133 @@ static void cancel_registration(struct sip_outbound_registration_client_state *c
 static pj_str_t PATH_NAME = { "path", 4 };
 static pj_str_t OUTBOUND_NAME = { "outbound", 8 };
 
+AST_VECTOR(pjsip_generic_string_hdr_vector, pjsip_generic_string_hdr *);
+
+/*!
+ * \internal
+ * \brief Callback function which finds a contact whose contact_status has security mechanisms.
+ *
+ * \param obj Pointer to the ast_sip_contact.
+ * \param arg Pointer-pointer to a contact_status that will be set to the contact_status found by this function.
+ * \param flags Flags used by the ao2_callback function.
+ *
+ * \note The refcount of the found contact_status must be decremented by the caller.
+ */
+static int contact_has_security_mechanisms(void *obj, void *arg, int flags)
+{
+	struct ast_sip_contact *contact = obj;
+	struct ast_sip_contact_status **ret = arg;
+	struct ast_sip_contact_status *contact_status = ast_sip_get_contact_status(contact);
+
+	if (!contact_status) {
+		return -1;
+	}
+	if (!AST_VECTOR_SIZE(&contact_status->security_mechanisms)) {
+		ao2_cleanup(contact_status);
+		return -1;
+	}
+	*ret = contact_status;
+	return 0;
+}
+
+static int contact_add_security_headers_to_status(void *obj, void *arg, int flags)
+{
+	struct ast_sip_contact *contact = obj;
+	struct pjsip_generic_string_hdr_vector *header_vector = arg;
+	struct ast_sip_contact_status *contact_status = ast_sip_get_contact_status(contact);
+
+	if (!contact_status) {
+		return -1;
+	}
+	if (AST_VECTOR_SIZE(&contact_status->security_mechanisms)) {
+		goto out;
+	}
+
+	ao2_lock(contact_status);
+	AST_VECTOR_CALLBACK_VOID(header_vector, ast_sip_header_to_security_mechanism, &contact_status->security_mechanisms);
+	ao2_unlock(contact_status);
+
+out:
+	ao2_cleanup(contact_status);
+	return 0;
+}
+
+/*! \brief Adds security negotiation mechanisms of outbound registration client state as Security headers to tdata. */
+static void add_security_headers(struct sip_outbound_registration_client_state *client_state,
+	pjsip_tx_data *tdata)
+{
+	int add_require_header = 1;
+	int add_proxy_require_header = 1;
+	int add_sec_client_header = 0;
+	struct sip_outbound_registration *reg = NULL;
+	struct ast_sip_endpoint *endpt = NULL;
+	struct ao2_container *contact_container;
+	struct ast_sip_contact_status *contact_status = NULL;
+	struct ast_sip_security_mechanism_vector *sec_mechs = NULL;
+	static const pj_str_t security_verify = { "Security-Verify", 15 };
+	static const pj_str_t security_client = { "Security-Client", 15 };
+	static const pj_str_t proxy_require = { "Proxy-Require", 13 };
+	static const pj_str_t require = { "Require", 7 };
+
+	if (client_state->security_negotiation != AST_SIP_SECURITY_NEG_MEDIASEC) {
+		return;
+	}
+
+	/* Get contact status through registration -> endpoint name -> aor -> contact (if set) */
+	if ((reg = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "registration", client_state->registration_name))
+		&& !ast_strlen_zero(reg->endpoint) && (endpt = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", reg->endpoint))
+		&& (contact_container = ast_sip_location_retrieve_contacts_from_aor_list(endpt->aors))) {
+		/* Retrieve all contacts associated with aors from this endpoint
+		 * and find the first one that has security mechanisms.
+		 */
+		ao2_callback(contact_container, 0, contact_has_security_mechanisms, &contact_status);
+		if (contact_status) {
+			ao2_lock(contact_status);
+			sec_mechs = &contact_status->security_mechanisms;
+		}
+		ao2_cleanup(contact_container);
+	}
+	/* Use client_state->server_security_mechanisms if contact_status does not exist. */
+	if (!contact_status && AST_VECTOR_SIZE(&client_state->server_security_mechanisms)) {
+		sec_mechs = &client_state->server_security_mechanisms;
+	}
+	if (client_state->status == SIP_REGISTRATION_REJECTED_TEMPORARY || client_state->auth_attempted) {
+		if (sec_mechs != NULL && pjsip_msg_find_hdr_by_name(tdata->msg, &security_verify, NULL) == NULL) {
+			ast_sip_add_security_headers(sec_mechs, "Security-Verify", 0, tdata);
+		}
+		if (client_state->last_status_code == 494) {
+			ast_sip_remove_headers_by_name_and_value(tdata->msg, &security_client, NULL);
+		} else {
+			/* necessary if a retry occures */
+			add_sec_client_header = (pjsip_msg_find_hdr_by_name(tdata->msg, &security_client, NULL) == NULL) ? 1 : 0;
+		}
+		add_require_header =
+			(pjsip_msg_find_hdr_by_name(tdata->msg, &require, NULL) == NULL) ? 1 : 0;
+		add_proxy_require_header =
+			(pjsip_msg_find_hdr_by_name(tdata->msg, &proxy_require, NULL) == NULL) ? 1 : 0;
+	} else {
+		ast_sip_add_security_headers(&client_state->security_mechanisms, "Security-Client", 0, tdata);
+	}
+
+	if (add_require_header) {
+		ast_sip_add_header(tdata, "Require", "mediasec");
+	}
+	if (add_proxy_require_header) {
+		ast_sip_add_header(tdata, "Proxy-Require", "mediasec");
+	}
+	if (add_sec_client_header) {
+		ast_sip_add_security_headers(&client_state->security_mechanisms, "Security-Client", 0, tdata);
+	}
+
+	/* Cleanup */
+	if (contact_status) {
+		ao2_unlock(contact_status);
+		ao2_cleanup(contact_status);
+	}
+	ao2_cleanup(endpt);
+	ao2_cleanup(reg);
+}
+
 /*! \brief Helper function which sends a message and cleans up, if needed, on failure */
 static pj_status_t registration_client_send(struct sip_outbound_registration_client_state *client_state,
 	pjsip_tx_data *tdata)
@@ -595,6 +759,9 @@ static pj_status_t registration_client_send(struct sip_outbound_registration_cli
 	 */
 	pjsip_tx_data_add_ref(tdata);
 
+	/* Add Security-Verify or Security-Client headers */
+	add_security_headers(client_state, tdata);
+
 	/*
 	 * Set the transport in case transports were reloaded.
 	 * When pjproject removes the extraneous error messages produced,
@@ -728,9 +895,9 @@ static int handle_client_registration(void *data)
 			ast_sip_add_header(tdata,"Proxy-Require","mediasec");
 			ast_sip_add_header(tdata,"Require","mediasec");
 
-			if(!AST_LIST_EMPTY(&endpoint->security_mechanisms)) {
+			if(!AST_LIST_EMPTY(&endpoint->secur_mechanisms)) {
 				struct security_mechanism *sec_mechanism;
-				AST_LIST_TRAVERSE(&endpoint->security_mechanisms, sec_mechanism, entry) {
+				AST_LIST_TRAVERSE(&endpoint->secur_mechanisms, sec_mechanism, entry) {
 					ast_debug(1, "Adding security header: %s\n", sec_mechanism->value);
 					ast_sip_add_header(tdata,"Security-Verify",sec_mechanism->value);
 				}
@@ -788,6 +955,7 @@ static void schedule_registration(struct sip_outbound_registration_client_state
 				(int) info.client_uri.slen, info.client_uri.ptr);
 		ao2_ref(client_state, -1);
 	}
+	client_state->registration_expires = ((int) time(NULL)) + seconds;
 }
 
 static void update_client_state_status(struct sip_outbound_registration_client_state *client_state, enum sip_outbound_registration_status status)
@@ -873,6 +1041,8 @@ static int handle_client_state_destruction(void *data)
 
 	update_client_state_status(client_state, SIP_REGISTRATION_STOPPED);
 	ast_sip_auth_vector_destroy(&client_state->outbound_auths);
+	ast_sip_security_mechanisms_vector_destroy(&client_state->security_mechanisms);
+	ast_sip_security_mechanisms_vector_destroy(&client_state->server_security_mechanisms);
 	ao2_ref(client_state, -1);
 
 	return 0;
@@ -892,6 +1062,8 @@ struct registration_response {
 	pjsip_rx_data *rdata;
 	/*! \brief Request for which the response was received */
 	pjsip_tx_data *old_request;
+	/*! \brief Key for the reliable transport in use */
+	char transport_key[IP6ADDR_COLON_PORT_BUFLEN];
 };
 
 /*! \brief Registration response structure destructor */
@@ -1041,13 +1213,10 @@ static int monitor_matcher(void *a, void *b)
 	return strcmp(ma, mb) == 0;
 }
 
-static void registration_transport_monitor_setup(pjsip_transport *transport, const char *registration_name)
+static void registration_transport_monitor_setup(const char *transport_key, const char *registration_name)
 {
 	char *monitor;
 
-	if (!PJSIP_TRANSPORT_IS_RELIABLE(transport)) {
-		return;
-	}
 	monitor = ao2_alloc_options(strlen(registration_name) + 1, NULL,
 		AO2_ALLOC_OPT_LOCK_NOLOCK);
 	if (!monitor) {
@@ -1060,8 +1229,8 @@ static void registration_transport_monitor_setup(pjsip_transport *transport, con
 	 * register the monitor.  We might get into a message spamming infinite
 	 * loop of registration, shutdown, reregistration...
 	 */
-	ast_sip_transport_monitor_register(transport, registration_transport_shutdown_cb,
-		monitor);
+	ast_sip_transport_monitor_register_replace_key(transport_key, registration_transport_shutdown_cb,
+		monitor, monitor_matcher);
 	ao2_ref(monitor, -1);
 }
 
@@ -1138,7 +1307,7 @@ static void clear_endpoint_security_mechanisms(struct ast_sip_endpoint *endpoint
 {
 	struct security_mechanism *sec_mechanism;
 
-	while ((sec_mechanism = AST_LIST_REMOVE_HEAD(&endpoint->security_mechanisms, entry))) {
+	while ((sec_mechanism = AST_LIST_REMOVE_HEAD(&endpoint->secur_mechanisms, entry))) {
 		ast_free(sec_mechanism);
 	}
 }
@@ -1218,6 +1387,7 @@ static int handle_registration_response(void *data)
 	pjsip_regc_get_info(response->client_state->client, &info);
 	ast_copy_pj_str(server_uri, &info.server_uri, sizeof(server_uri));
 	ast_copy_pj_str(client_uri, &info.client_uri, sizeof(client_uri));
+	response->client_state->last_status_code = response->code;
 
 	ast_debug(1, "Processing REGISTER response %d from server '%s' for client '%s'\n",
 			response->code, server_uri, client_uri);
@@ -1234,7 +1404,7 @@ static int handle_registration_response(void *data)
 				ast_log(LOG_ERROR, "No endpoint found to store mediasec headers\n");
 				return -1;
 			}
-			if(AST_LIST_EMPTY(&endpoint->security_mechanisms)) {
+			if(AST_LIST_EMPTY(&endpoint->secur_mechanisms)) {
 				secSrv = pjsip_msg_find_hdr_by_name(response->rdata->msg_info.msg, &headerName, NULL);
 				struct security_mechanism *sec_mechanism;
 				while (secSrv) {
@@ -1247,7 +1417,7 @@ static int handle_registration_response(void *data)
 						return -1;
 					}
 					ast_copy_pj_str(&sec_mechanism->value, &secSrv->hvalue, pj_strlen(&secSrv->hvalue) + 1);
-					AST_LIST_INSERT_TAIL(&endpoint->security_mechanisms, sec_mechanism, entry);
+					AST_LIST_INSERT_TAIL(&endpoint->secur_mechanisms, sec_mechanism, entry);
 					ast_debug(1, "Store \"Security-Verify\" header: %s\n", sec_mechanism->value);
 					secSrv = pjsip_msg_find_hdr_by_name(response->rdata->msg_info.msg, &headerName, secSrv->next);
 				}
@@ -1265,6 +1435,8 @@ static int handle_registration_response(void *data)
 				return 0;
 			}
 		}
+	// change the condition to use iopsys mediasec implementation instead of asterisk one
+	//} else if ((response->code == 401 || response->code == 407 || response->code == 494)
 	} else if ((response->code == 401 || response->code == 407)
 		&& (!response->client_state->auth_attempted
 			|| response->rdata->msg_info.cseq->cseq != response->client_state->auth_cseq)) {
@@ -1272,6 +1444,48 @@ static int handle_registration_response(void *data)
 		pjsip_cseq_hdr *cseq_hdr;
 		pjsip_tx_data *tdata;
 
+		// comment out this code to use iopsys mediasec implementation instead of asterisk one
+		//if (response->client_state->security_negotiation == AST_SIP_SECURITY_NEG_MEDIASEC) {
+		//	struct sip_outbound_registration *reg = NULL;
+		//	struct ast_sip_endpoint *endpt = NULL;
+		//	struct ao2_container *contact_container = NULL;
+		//	pjsip_generic_string_hdr *header;
+		//	struct pjsip_generic_string_hdr_vector header_vector;
+		//	static const pj_str_t security_server = { "Security-Server", 15 };
+		//
+		//	if ((reg = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "registration",
+		//		response->client_state->registration_name)) && reg->endpoint &&
+		//		(endpt = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", reg->endpoint))) {
+		//		/* Retrieve all contacts associated with aors from this endpoint (if set). */
+		//		contact_container = ast_sip_location_retrieve_contacts_from_aor_list(endpt->aors);
+		//	}
+		//	/* Add server list of security mechanism to client_state and contact status if exists. */
+		//	AST_VECTOR_INIT(&header_vector, 1);
+		//	header = pjsip_msg_find_hdr_by_name(response->rdata->msg_info.msg, &security_server, NULL);
+		//	for (; header;
+		//		header = pjsip_msg_find_hdr_by_name(response->rdata->msg_info.msg, &security_server, header->next)) {
+		//		AST_VECTOR_APPEND(&header_vector, header);
+		//		ast_sip_header_to_security_mechanism(header, &response->client_state->server_security_mechanisms);
+		//	}
+		//	if (contact_container) {
+		//		/* Add server security mechanisms to contact status of all associated contacts to be able to send correct
+		//		 * Security-Verify headers on subsequent non-REGISTER requests through this outbound registration.
+		//		 */
+		//		ao2_callback(contact_container, OBJ_NODATA, contact_add_security_headers_to_status, &header_vector);
+		//		ao2_cleanup(contact_container);
+		//	}
+		//	AST_VECTOR_FREE(&header_vector);
+		//	ao2_cleanup(endpt);
+		//	ao2_cleanup(reg);
+		//}
+		//
+		//if (response->code == 494) {
+		//	update_client_state_status(response->client_state, SIP_REGISTRATION_REJECTED_TEMPORARY);
+		//	response->client_state->retries++;
+		//	schedule_registration(response->client_state, 0);
+		//	ao2_ref(response, -1);
+		//	return 0;
+		//} else if (!ast_sip_create_request_with_auth(&response->client_state->outbound_auths,
 		if (!ast_sip_create_request_with_auth(&response->client_state->outbound_auths,
 				response->rdata, response->old_request, &tdata)) {
 			response->client_state->auth_attempted = 1;
@@ -1287,9 +1501,9 @@ static int handle_registration_response(void *data)
 					ast_log(LOG_ERROR, "No endpoint found to store/add mediasec headers\n");
 					return -1;
 				}
-				if(!AST_LIST_EMPTY(&endpoint->security_mechanisms)) {
+				if(!AST_LIST_EMPTY(&endpoint->secur_mechanisms)) {
 					struct security_mechanism *sec_mechanism;
-					AST_LIST_TRAVERSE(&endpoint->security_mechanisms, sec_mechanism, entry) {
+					AST_LIST_TRAVERSE(&endpoint->secur_mechanisms, sec_mechanism, entry) {
 						ast_debug(1, "Adding security header: %s\n", sec_mechanism->value);
 						ast_sip_add_header(tdata,"Security-Verify",sec_mechanism->value);
 					}
@@ -1297,6 +1511,7 @@ static int handle_registration_response(void *data)
 			}
 
 			pjsip_tx_data_add_ref(tdata);
+
 			res = registration_client_send(response->client_state, tdata);
 
 			/* Save the cseq that actually got sent. */
@@ -1336,16 +1551,20 @@ static int handle_registration_response(void *data)
 			schedule_registration(response->client_state, next_registration_round);
 
 			/* See if we should monitor for transport shutdown */
-			registration_transport_monitor_setup(response->rdata->tp_info.transport,
-				response->client_state->registration_name);
+			if (PJSIP_TRANSPORT_IS_RELIABLE(response->rdata->tp_info.transport)) {
+				registration_transport_monitor_setup(response->transport_key,
+					response->client_state->registration_name);
+			}
 		} else {
 			ast_debug(1, "Outbound unregistration to '%s' with client '%s' successful\n", server_uri, client_uri);
 			response->client_state->is494=0;
 			update_client_state_status(response->client_state, SIP_REGISTRATION_UNREGISTERED);
 			sip_outbound_registration_send_ubus_event("UNREGISTERED",response->expiration,client_uri);
-			ast_sip_transport_monitor_unregister(response->rdata->tp_info.transport,
-				registration_transport_shutdown_cb, response->client_state->registration_name,
-				monitor_matcher);
+			if (PJSIP_TRANSPORT_IS_RELIABLE(response->rdata->tp_info.transport)) {
+				ast_sip_transport_monitor_unregister_key(response->transport_key,
+					registration_transport_shutdown_cb, response->client_state->registration_name,
+					monitor_matcher);
+			}
 		}
 
 		save_response_fields_to_transport(response);
@@ -1515,6 +1734,9 @@ static void sip_outbound_registration_response_cb(struct pjsip_regc_cbparam *par
 		response->old_request = tsx->last_tx;
 		pjsip_tx_data_add_ref(response->old_request);
 		pjsip_rx_data_clone(param->rdata, 0, &response->rdata);
+		AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(param->rdata->tp_info.transport,
+			response->transport_key);
+
 	} else {
 		/* old_request steals the reference */
 		response->old_request = client_state->last_tdata;
@@ -1625,6 +1847,7 @@ static void sip_outbound_registration_destroy(void *obj)
 	struct sip_outbound_registration *registration = obj;
 
 	ast_sip_auth_vector_destroy(&registration->outbound_auths);
+	ast_sip_security_mechanisms_vector_destroy(&registration->security_mechanisms);
 
 	ast_string_field_free_memory(registration);
 }
@@ -1970,9 +2193,12 @@ static int sip_outbound_registration_perform(void *data)
 	struct sip_outbound_registration_state *state = data;
 	struct sip_outbound_registration *registration = ao2_bump(state->registration);
 	size_t i;
+	int max_delay;
 
 	/* Just in case the client state is being reused for this registration, free the auth information */
 	ast_sip_auth_vector_destroy(&state->client_state->outbound_auths);
+	ast_sip_security_mechanisms_vector_destroy(&state->client_state->security_mechanisms);
+	ast_sip_security_mechanisms_vector_destroy(&state->client_state->server_security_mechanisms);
 
 	AST_VECTOR_INIT(&state->client_state->outbound_auths, AST_VECTOR_SIZE(&registration->outbound_auths));
 	for (i = 0; i < AST_VECTOR_SIZE(&registration->outbound_auths); ++i) {
@@ -1982,6 +2208,8 @@ static int sip_outbound_registration_perform(void *data)
 			ast_free(name);
 		}
 	}
+	ast_sip_security_mechanisms_vector_copy(&state->client_state->security_mechanisms,
+											&registration->security_mechanisms);
 	state->client_state->retry_interval = registration->retry_interval;
 	state->client_state->forbidden_retry_interval = registration->forbidden_retry_interval;
 	state->client_state->fatal_retry_interval = registration->fatal_retry_interval;
@@ -1990,11 +2218,14 @@ static int sip_outbound_registration_perform(void *data)
 	state->client_state->support_path = registration->support_path;
 	state->client_state->mediasec = registration->mediasec;
 	state->client_state->support_outbound = registration->support_outbound;
+	state->client_state->security_negotiation = registration->security_negotiation;
 	state->client_state->auth_rejection_permanent = registration->auth_rejection_permanent;
+	max_delay = registration->max_random_initial_delay;
 
 	pjsip_regc_update_expires(state->client_state->client, registration->expiration);
 
-	schedule_registration(state->client_state, (ast_random() % 10) + 1);
+	/* n mod 0 is undefined, so don't let that happen */
+	schedule_registration(state->client_state, (max_delay ? ast_random() % max_delay : 0) + 1);
 
 	ao2_ref(registration, -1);
 	ao2_ref(state, -1);
@@ -2022,7 +2253,7 @@ static int sip_outbound_registration_apply(const struct ast_sorcery *sorcery, vo
 			ast_sorcery_object_get_id(applied));
 		return -1;
 	} else if (ast_sip_validate_uri_length(applied->server_uri)) {
-			ast_log(LOG_ERROR, "Server URI or hostname length exceeds pjpropject limit '%s'\n",
+			ast_log(LOG_ERROR, "Server URI or hostname length exceeds pjproject limit '%s'\n",
 				ast_sorcery_object_get_id(applied));
 			return -1;
 	} else if (ast_strlen_zero(applied->client_uri)) {
@@ -2030,7 +2261,7 @@ static int sip_outbound_registration_apply(const struct ast_sorcery *sorcery, vo
 			ast_sorcery_object_get_id(applied));
 		return -1;
 	} else if (ast_sip_validate_uri_length(applied->client_uri)) {
-			ast_log(LOG_ERROR, "Client URI or hostname length exceeds pjpropject limit '%s'\n",
+			ast_log(LOG_ERROR, "Client URI or hostname length exceeds pjproject limit '%s'\n",
 				ast_sorcery_object_get_id(applied));
 			return -1;
 	} else if (applied->line && ast_strlen_zero(applied->endpoint)) {
@@ -2086,6 +2317,41 @@ static int sip_outbound_registration_apply(const struct ast_sorcery *sorcery, vo
 	return 0;
 }
 
+static int security_mechanism_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct sip_outbound_registration *registration = obj;
+
+	return ast_sip_security_mechanisms_to_str(&registration->security_mechanisms, 0, buf);
+}
+
+static const char *security_negotiation_map[] = {
+	[AST_SIP_SECURITY_NEG_NONE] = "no",
+	[AST_SIP_SECURITY_NEG_MEDIASEC] = "mediasec",
+};
+
+static int security_negotiation_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct sip_outbound_registration *registration = obj;
+	if (ARRAY_IN_BOUNDS(registration->security_negotiation, security_negotiation_map)) {
+		*buf = ast_strdup(security_negotiation_map[registration->security_negotiation]);
+	}
+	return 0;
+}
+
+static int security_mechanisms_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct sip_outbound_registration *registration = obj;
+
+	return ast_sip_security_mechanism_vector_init(&registration->security_mechanisms, var->value);
+}
+
+static int security_negotiation_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct sip_outbound_registration *registration = obj;
+
+	return ast_sip_set_security_negotiation(&registration->security_negotiation, var->value);
+}
+
 static int outbound_auth_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct sip_outbound_registration *registration = obj;
@@ -2532,7 +2798,7 @@ static int cli_print_header(void *obj, void *arg, int flags)
 	ast_assert(context->output_buffer != NULL);
 
 	ast_str_append(&context->output_buffer, 0,
-		" <Registration/ServerURI..............................>  <Auth..........>  <Status.......>\n");
+		" <Registration/ServerURI..............................>  <Auth....................>  <Status.......>\n");
 
 	return 0;
 }
@@ -2543,11 +2809,13 @@ static int cli_print_body(void *obj, void *arg, int flags)
 	struct ast_sip_cli_context *context = arg;
 	const char *id = ast_sorcery_object_get_id(registration);
 	struct sip_outbound_registration_state *state = get_state(id);
+	int expsecs;
 #define REGISTRATION_URI_FIELD_LEN	53
 
 	ast_assert(context->output_buffer != NULL);
+	expsecs = state ? state->client_state->registration_expires - ((int) time(NULL)) : 0;
 
-	ast_str_append(&context->output_buffer, 0, " %-s/%-*.*s  %-16s  %-16s\n",
+	ast_str_append(&context->output_buffer, 0, " %-s/%-*.*s  %-26s  %-16s %s%d%s\n",
 		id,
 		(int) (REGISTRATION_URI_FIELD_LEN - strlen(id)),
 		(int) (REGISTRATION_URI_FIELD_LEN - strlen(id)),
@@ -2555,7 +2823,8 @@ static int cli_print_body(void *obj, void *arg, int flags)
 		AST_VECTOR_SIZE(&registration->outbound_auths)
 			? AST_VECTOR_GET(&registration->outbound_auths, 0)
 			: "n/a",
-		(state ? sip_outbound_registration_status_str(state->client_state->status) : "Unregistered"));
+		(state ? sip_outbound_registration_status_str(state->client_state->status) : "Unregistered"),
+		state ? " (exp. " : "", abs(expsecs), state ? (expsecs < 0 ? "s ago)" : "s)") : "");
 	ao2_cleanup(state);
 
 	if (context->show_details
@@ -2811,6 +3080,7 @@ static int load_module(void)
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "transport", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, transport));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, outbound_proxy));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "expiration", "3600", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, expiration));
+	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "max_random_initial_delay", "10", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, max_random_initial_delay));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "retry_interval", "1", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, retry_interval));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "forbidden_retry_interval", "0", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, forbidden_retry_interval));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "fatal_retry_interval", "0", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, fatal_retry_interval));
@@ -2819,6 +3089,8 @@ static int load_module(void)
 	ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "outbound_auth", "", outbound_auth_handler, outbound_auths_to_str, outbound_auths_to_var_list, 0, 0);
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "support_path", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, support_path));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "support_outbound", "no", OPT_YESNO_T, 1, FLDSET(struct sip_outbound_registration, support_outbound));
+	ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "security_negotiation", "no", security_negotiation_handler, security_negotiation_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "security_mechanisms", "", security_mechanisms_handler, security_mechanism_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "line", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, line));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, endpoint));
 	ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "mediasec", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, mediasec));
diff --git a/res/res_pjsip_path.c b/res/res_pjsip_path.c
index 5eb3d49e05944931b48054583f2f16a2c997e872..9492ef1697ed58115612e1e73056a3192319fbbe 100644
--- a/res/res_pjsip_path.c
+++ b/res/res_pjsip_path.c
@@ -36,68 +36,16 @@
 static const pj_str_t PATH_NAME = { "Path", 4 };
 static pj_str_t PATH_SUPPORTED_NAME = { "path", 4 };
 
-static struct ast_sip_aor *find_aor(struct ast_sip_endpoint *endpoint, pjsip_uri *uri)
+static struct ast_sip_aor *find_aor(struct ast_sip_contact *contact)
 {
-	char *configured_aors, *aor_name;
-	pjsip_sip_uri *sip_uri;
-	char *domain_name;
-	char *username;
-	struct ast_str *id = NULL;
-
-	if (ast_strlen_zero(endpoint->aors)) {
+	if (!contact) {
 		return NULL;
 	}
-
-	sip_uri = pjsip_uri_get_uri(uri);
-	domain_name = ast_alloca(sip_uri->host.slen + 1);
-	ast_copy_pj_str(domain_name, &sip_uri->host, sip_uri->host.slen + 1);
-	username = ast_alloca(sip_uri->user.slen + 1);
-	ast_copy_pj_str(username, &sip_uri->user, sip_uri->user.slen + 1);
-
-	/*
-	 * We may want to match without any user options getting
-	 * in the way.
-	 */
-	AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(username);
-
-	configured_aors = ast_strdupa(endpoint->aors);
-
-	/* Iterate the configured AORs to see if the user or the user+domain match */
-	while ((aor_name = ast_strip(strsep(&configured_aors, ",")))) {
-		struct ast_sip_domain_alias *alias = NULL;
-
-		if (ast_strlen_zero(aor_name)) {
-			continue;
-		}
-
-		if (!strcmp(username, aor_name)) {
-			break;
-		}
-
-		if (!id && !(id = ast_str_create(strlen(username) + sip_uri->host.slen + 2))) {
-			aor_name = NULL;
-			break;
-		}
-
-		ast_str_set(&id, 0, "%s@", username);
-		if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) {
-			ast_str_append(&id, 0, "%s", alias->domain);
-			ao2_cleanup(alias);
-		} else {
-			ast_str_append(&id, 0, "%s", domain_name);
-		}
-
-		if (!strcmp(aor_name, ast_str_buffer(id))) {
-			break;
-		}
-	}
-	ast_free(id);
-
-	if (ast_strlen_zero(aor_name)) {
+	if (ast_strlen_zero(contact->aor)) {
 		return NULL;
 	}
 
-	return ast_sip_location_retrieve_aor(aor_name);
+	return ast_sip_location_retrieve_aor(contact->aor);
 }
 
 /*!
@@ -170,7 +118,7 @@ static void path_outgoing_request(struct ast_sip_endpoint *endpoint, struct ast_
 		return;
 	}
 
-	aor = find_aor(endpoint, tdata->msg->line.req.uri);
+	aor = find_aor(contact);
 	if (!aor || !aor->support_path) {
 		return;
 	}
@@ -202,7 +150,6 @@ static void path_outgoing_response(struct ast_sip_endpoint *endpoint, struct ast
 	struct pjsip_status_line status = tdata->msg->line.status;
 	pj_str_t path_dup;
 	pjsip_generic_string_hdr *path_hdr;
-	pjsip_contact_hdr *contact_hdr;
 	RAII_VAR(struct ast_sip_aor *, aor, NULL, ao2_cleanup);
 	pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
 	const pj_str_t REGISTER_METHOD = {"REGISTER", 8};
@@ -213,12 +160,7 @@ static void path_outgoing_response(struct ast_sip_endpoint *endpoint, struct ast
 		return;
 	}
 
-	contact_hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
-	if (!contact_hdr) {
-		return;
-	}
-
-	aor = find_aor(endpoint, contact_hdr->uri);
+	aor = find_aor(contact);
 	if (!aor || !aor->support_path || add_supported(tdata)
 		|| path_get_string(tdata->pool, contact, &path_dup)) {
 		return;
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index a2427fde47e8aedd9483a23e4540623576434c04..c90398a4471dc53bae38d18f4d44726f316a4e22 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -393,8 +393,8 @@ struct subscription_persistence {
 	char src_name[PJ_INET6_ADDRSTRLEN];
 	/*! Source port of the message */
 	int src_port;
-	/*! Local transport key type */
-	char transport_key[32];
+	/*! Local transport type (UDP,TCP,TLS)*/
+	char transport_type[32];
 	/*! Local transport address */
 	char local_name[PJ_INET6_ADDRSTRLEN];
 	/*! Local transport port */
@@ -478,7 +478,7 @@ struct sip_subscription_tree {
 	/*! The transport the subscription was received on.
 	 * Only used for reliable transports.
 	 */
-	pjsip_transport *transport;
+	char transport_key[IP6ADDR_COLON_PORT_BUFLEN];
 	/*! Indicator if initial notify should be generated.
 	 * Used to refresh modified RLS.
 	 */
@@ -787,8 +787,9 @@ static void subscription_persistence_update(struct sip_subscription_tree *sub_tr
 							rdata->tp_info.transport->obj_name,
 							sub_tree->persistence->endpoint, sub_tree->root->resource,
 							sub_tree->persistence->prune_on_boot);
-						sub_tree->transport = rdata->tp_info.transport;
-						ast_sip_transport_monitor_register(rdata->tp_info.transport,
+						AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(rdata->tp_info.transport,
+							sub_tree->transport_key);
+						ast_sip_transport_monitor_register_key(sub_tree->transport_key,
 							sub_tree_transport_cb, sub_tree);
 						/*
 						 * FYI: ast_sip_transport_monitor_register holds a reference to the sub_tree
@@ -822,8 +823,8 @@ static void subscription_persistence_update(struct sip_subscription_tree *sub_tr
 		ast_copy_string(sub_tree->persistence->src_name, rdata->pkt_info.src_name,
 				sizeof(sub_tree->persistence->src_name));
 		sub_tree->persistence->src_port = rdata->pkt_info.src_port;
-		ast_copy_string(sub_tree->persistence->transport_key, rdata->tp_info.transport->type_name,
-			sizeof(sub_tree->persistence->transport_key));
+		ast_copy_string(sub_tree->persistence->transport_type, rdata->tp_info.transport->type_name,
+			sizeof(sub_tree->persistence->transport_type));
 		ast_copy_pj_str(sub_tree->persistence->local_name, &rdata->tp_info.transport->local_name.host,
 			sizeof(sub_tree->persistence->local_name));
 		sub_tree->persistence->local_port = rdata->tp_info.transport->local_name.port;
@@ -839,12 +840,12 @@ static void subscription_persistence_remove(struct sip_subscription_tree *sub_tr
 		return;
 	}
 
-	if (sub_tree->persistence->prune_on_boot && sub_tree->transport) {
+	if (sub_tree->persistence->prune_on_boot && !ast_strlen_zero(sub_tree->transport_key)) {
 		ast_debug(3, "Unregistering transport monitor on %s '%s->%s'\n",
-			sub_tree->transport->obj_name,
+			sub_tree->transport_key,
 			sub_tree->endpoint ? ast_sorcery_object_get_id(sub_tree->endpoint) : "Unknown",
 			sub_tree->root ? sub_tree->root->resource : "Unknown");
-		ast_sip_transport_monitor_unregister(sub_tree->transport,
+		ast_sip_transport_monitor_unregister_key(sub_tree->transport_key,
 			sub_tree_transport_cb, sub_tree, NULL);
 	}
 
@@ -1020,6 +1021,8 @@ static struct resource_list *retrieve_resource_list(const char *resource, const
  * \param resource The name of the resource for this tree node.
  * \param visited The vector of resources that have been visited.
  * \param full_state if allocating a list, indicate whether full state is requested in notifications.
+ * \param display_name the display name to include with this tree node.
+ *
  * \retval NULL Allocation failure.
  * \retval non-NULL The newly-allocated tree_node
  */
@@ -1333,7 +1336,14 @@ static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_s
 		const char *resource, const char *display_name, struct sip_subscription_tree *tree)
 {
 	struct ast_sip_subscription *sub;
-	pjsip_sip_uri *contact_uri;
+	pjsip_msg *msg;
+	pjsip_sip_uri *request_uri;
+
+	msg = ast_sip_mod_data_get(tree->dlg->mod_data, pubsub_module.id, MOD_DATA_MSG);
+	if (!msg) {
+		ast_log(LOG_ERROR, "No dialog message saved for SIP subscription. Cannot allocate subscription for resource %s\n", resource);
+		return NULL;
+	}
 
 	sub = ast_calloc(1, sizeof(*sub) + strlen(resource) + 1);
 	if (!sub) {
@@ -1356,8 +1366,8 @@ static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_s
 	}
 
 	sub->uri = pjsip_sip_uri_create(tree->dlg->pool, PJ_FALSE);
-	contact_uri = pjsip_uri_get_uri(tree->dlg->local.contact->uri);
-	pjsip_sip_uri_assign(tree->dlg->pool, sub->uri, contact_uri);
+	request_uri = pjsip_uri_get_uri(msg->line.req.uri);
+	pjsip_sip_uri_assign(tree->dlg->pool, sub->uri, request_uri);
 	pj_strdup2(tree->dlg->pool, &sub->uri->user, resource);
 
 	/* If there is any persistence information available for this subscription that was persisted
@@ -1665,17 +1675,17 @@ static int sub_persistence_recreate(void *obj)
 	struct ast_sip_pubsub_body_generator *generator;
 	struct ast_sip_subscription_handler *handler;
 	char *resource;
-	pjsip_sip_uri *request_uri;
 	size_t resource_size;
 	int resp;
 	struct resource_tree tree;
 	pjsip_expires_hdr *expires_header;
 	int64_t expires;
+	const pj_str_t *user;
 
-	request_uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);
-	resource_size = pj_strlen(&request_uri->user) + 1;
+	user = ast_sip_pjsip_uri_get_username(rdata->msg_info.msg->line.req.uri);
+	resource_size = pj_strlen(user) + 1;
 	resource = ast_alloca(resource_size);
-	ast_copy_pj_str(resource, &request_uri->user, resource_size);
+	ast_copy_pj_str(resource, user, resource_size);
 
 	/*
 	 * We may want to match without any user options getting
@@ -1812,7 +1822,7 @@ static int subscription_persistence_recreate(void *obj, void *arg, int flags)
 	rdata.tp_info.pool = pool;
 
 	if (ast_sip_create_rdata_with_contact(&rdata, persistence->packet, persistence->src_name,
-		persistence->src_port, persistence->transport_key, persistence->local_name,
+		persistence->src_port, persistence->transport_type, persistence->local_name,
 		persistence->local_port, persistence->contact_uri)) {
 		ast_log(LOG_WARNING, "Failed recreating '%s' subscription: The message could not be parsed\n",
 			persistence->endpoint);
@@ -2139,6 +2149,7 @@ static void add_rlmi_resource(pj_pool_t *pool, pj_xml_node *rlmi, const pjsip_ge
 	pj_xml_attr *cid_attr;
 	char id[6];
 	char uri[PJSIP_MAX_URL_SIZE];
+	char name_sanitized[PJSIP_MAX_URL_SIZE];
 
 	/* This creates a string representing the Content-ID without the enclosing < > */
 	const pj_str_t cid_stripped = {
@@ -2153,7 +2164,8 @@ static void add_rlmi_resource(pj_pool_t *pool, pj_xml_node *rlmi, const pjsip_ge
 	pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, resource_uri, uri, sizeof(uri));
 	ast_sip_presence_xml_create_attr(pool, resource, "uri", uri);
 
-	pj_strdup2(pool, &name->content, resource_name);
+	ast_sip_sanitize_xml(resource_name, name_sanitized, sizeof(name_sanitized));
+	pj_strdup2(pool, &name->content, name_sanitized);
 
 	ast_generate_random_string(id, sizeof(id));
 
@@ -3082,11 +3094,11 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
 	struct ast_sip_pubsub_body_generator *generator;
 	char *resource;
 	pjsip_uri *request_uri;
-	pjsip_sip_uri *request_uri_sip;
 	size_t resource_size;
 	int resp;
 	struct resource_tree tree;
 	pj_status_t dlg_status;
+	const pj_str_t *user;
 
 	endpoint = ast_pjsip_rdata_get_endpoint(rdata);
 	ast_assert(endpoint != NULL);
@@ -3099,7 +3111,7 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
 
 	request_uri = rdata->msg_info.msg->line.req.uri;
 
-	if (!PJSIP_URI_SCHEME_IS_SIP(request_uri) && !PJSIP_URI_SCHEME_IS_SIPS(request_uri)) {
+	if (!ast_sip_is_uri_sip_sips(request_uri)) {
 		char uri_str[PJSIP_MAX_URL_SIZE];
 
 		pjsip_uri_print(PJSIP_URI_IN_REQ_URI, request_uri, uri_str, sizeof(uri_str));
@@ -3108,10 +3120,10 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
 		return PJ_TRUE;
 	}
 
-	request_uri_sip = pjsip_uri_get_uri(request_uri);
-	resource_size = pj_strlen(&request_uri_sip->user) + 1;
+	user = ast_sip_pjsip_uri_get_username(request_uri);
+	resource_size = pj_strlen(user) + 1;
 	resource = ast_alloca(resource_size);
-	ast_copy_pj_str(resource, &request_uri_sip->user, resource_size);
+	ast_copy_pj_str(resource, user, resource_size);
 
 	/*
 	 * We may want to match without any user options getting
@@ -3327,12 +3339,12 @@ static struct ast_sip_publication *publish_request_initial(struct ast_sip_endpoi
 	RAII_VAR(struct ast_sip_publication_resource *, resource, NULL, ao2_cleanup);
 	struct ast_variable *event_configuration_name = NULL;
 	pjsip_uri *request_uri;
-	pjsip_sip_uri *request_uri_sip;
 	int resp;
+	const pj_str_t *user;
 
 	request_uri = rdata->msg_info.msg->line.req.uri;
 
-	if (!PJSIP_URI_SCHEME_IS_SIP(request_uri) && !PJSIP_URI_SCHEME_IS_SIPS(request_uri)) {
+	if (!ast_sip_is_uri_sip_sips(request_uri)) {
 		char uri_str[PJSIP_MAX_URL_SIZE];
 
 		pjsip_uri_print(PJSIP_URI_IN_REQ_URI, request_uri, uri_str, sizeof(uri_str));
@@ -3341,10 +3353,10 @@ static struct ast_sip_publication *publish_request_initial(struct ast_sip_endpoi
 		return NULL;
 	}
 
-	request_uri_sip = pjsip_uri_get_uri(request_uri);
-	resource_size = pj_strlen(&request_uri_sip->user) + 1;
+	user = ast_sip_pjsip_uri_get_username(request_uri);
+	resource_size = pj_strlen(user) + 1;
 	resource_name = ast_alloca(resource_size);
-	ast_copy_pj_str(resource_name, &request_uri_sip->user, resource_size);
+	ast_copy_pj_str(resource_name, user, resource_size);
 
 	/*
 	 * We may want to match without any user options getting
@@ -3680,7 +3692,8 @@ end:
 	return res;
 }
 
-static int parse_simple_message_summary(char *body, struct simple_message_summary *summary)
+static int parse_simple_message_summary(char *body,
+	struct simple_message_summary *summary)
 {
 	char *line;
 	char *buffer;
@@ -3841,15 +3854,38 @@ static void set_state_terminated(struct ast_sip_subscription *sub)
 /*!
  * \brief Callback sequence for subscription terminate:
  *
+ * * Please note that the descriptions below represent pjproject behavior on versions
+ *   >= 2.13.
  * * Client initiated:
  *     pjproject receives SUBSCRIBE on the subscription's serializer thread
+ *         calls pubsub_evsub_set_state with state = TERMINATED
+ *             pubsub_on_evsub_state checks the event and finds it is due to a received
+ *             SUBSCRIBE with an expires of 0 and so does nothing.
  *         calls pubsub_on_rx_refresh with dialog locked
  *             pubsub_on_rx_refresh sets TERMINATE_PENDING
+ *             calls pubsub_on_refresh_timeout to push final NOTIFY to pjproject
+ *                 checks state == TERMINATE_PENDING
+ *                 sets TERMINATE_IN_PROGRESS
+ *                 calls send_notify (2)
+ *                 send_notify ultimately calls pjsip_evsub_send_request
+ *                 pjsip_evsub_send_request calls evsub's set_state
+ *                     set_state calls pubsub_evsub_set_state
+ *                         pubsub_on_evsub_state checks state == TERMINATE_IN_PROGRESS
+ *                         removes the subscriptions
+ *                         cleans up references to evsub
+ *                         sets state = TERMINATED
+ *             pubsub_on_refresh_timeout unlocks dialog
+ *             returns to pjproject
+ *         pjproject unlocks dialog
+ *
+ * * Subscription timer expires:
+ *     pjproject timer expires
+ *         locks dialog
+ *         calls pubsub_on_server_timeout
+ *             pubsub_on_server_timeout checks state == NORMAL
+ *             sets TERMINATE_PENDING
  *             pushes serialized_pubsub_on_refresh_timeout
  *             returns to pjproject
- *         pjproject calls pubsub_on_evsub_state
- *             pubsub_evsub_set_state checks state == TERMINATE_IN_PROGRESS (no)
- *             ignore and return
  *         pjproject unlocks dialog
  *     serialized_pubsub_on_refresh_timeout starts (1)
  *       locks dialog
@@ -3860,23 +3896,12 @@ static void set_state_terminated(struct ast_sip_subscription *sub)
  *               pjsip_evsub_send_request calls evsub's set_state
  *                   set_state calls pubsub_evsub_set_state
  *                       pubsub_on_evsub_state checks state == TERMINATE_IN_PROGRESS
+ *                       checks that the event is not due to un-SUBSCRIBE
  *                       removes the subscriptions
  *                       cleans up references to evsub
  *                       sets state = TERMINATED
  *       serialized_pubsub_on_refresh_timeout unlocks dialog
  *
- * * Subscription timer expires:
- *     pjproject timer expires
- *         locks dialog
- *         calls pubsub_on_server_timeout
- *             pubsub_on_server_timeout checks state == NORMAL
- *             sets TERMINATE_PENDING
- *             pushes serialized_pubsub_on_refresh_timeout
- *             returns to pjproject
- *         pjproject unlocks dialog
- *     serialized_pubsub_on_refresh_timeout starts
- *         See (1) Above
- *
  * * Transmission failure sending NOTIFY or error response from client
  *     pjproject transaction timer expires or non OK response
  *         pjproject locks dialog
@@ -3906,35 +3931,14 @@ static void set_state_terminated(struct ast_sip_subscription *sub)
  *
  */
 
-/*!
- * \brief PJSIP callback when underlying SIP subscription changes state
- *
- * Although this function is called for every state change, we only care
- * about the TERMINATED state, and only when we're actually processing the final
- * notify (SIP_SUB_TREE_TERMINATE_IN_PROGRESS) OR when a transmission failure
- * occurs (PJSIP_EVENT_TSX_STATE).  In this case, we do all the subscription tree
- * cleanup tasks and decrement the evsub reference.
- */
-static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
-{
-	struct sip_subscription_tree *sub_tree =
-		pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
 
-	ast_debug(3, "evsub %p state %s event %s sub_tree %p sub_tree state %s\n", evsub,
-		pjsip_evsub_get_state_name(evsub), pjsip_event_str(event->type), sub_tree,
-		(sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
+/* The code in this function was previously in pubsub_on_evsub_state. */
+static void clean_sub_tree(pjsip_evsub *evsub){
 
-	if (!sub_tree || pjsip_evsub_get_state(evsub) != PJSIP_EVSUB_STATE_TERMINATED) {
-		return;
-	}
+	struct sip_subscription_tree *sub_tree;
+	sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
 
-	/* It's easier to write this as what we WANT to process, then negate it. */
-	if (!(sub_tree->state == SIP_SUB_TREE_TERMINATE_IN_PROGRESS
-		|| (event->type == PJSIP_EVENT_TSX_STATE && sub_tree->state == SIP_SUB_TREE_NORMAL)
-		)) {
-		ast_debug(3, "Do nothing.\n");
-		return;
-	}
+	ast_debug(3, "Cleaning subscription %p\n", evsub);
 
 	if (sub_tree->expiration_task) {
 		char task_name[256];
@@ -3967,6 +3971,55 @@ static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
 	ao2_ref(sub_tree, -1);
 }
 
+/*!
+ * \brief PJSIP callback when underlying SIP subscription changes state
+ *
+ * Although this function is called for every state change, we only care
+ * about the TERMINATED state, and only when we're actually processing the final
+ * notify (SIP_SUB_TREE_TERMINATE_IN_PROGRESS) OR when a transmission failure
+ * occurs (PJSIP_EVENT_TSX_STATE).  In this case, we do all the subscription tree
+ * cleanup tasks and decrement the evsub reference.
+ */
+static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
+{
+	struct sip_subscription_tree *sub_tree =
+		pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
+
+	ast_debug(3, "evsub %p state %s event %s sub_tree %p sub_tree state %s\n", evsub,
+		pjsip_evsub_get_state_name(evsub), pjsip_event_str(event->type), sub_tree,
+		(sub_tree ? sub_tree_state_description[sub_tree->state] : "UNKNOWN"));
+
+	if (!sub_tree || pjsip_evsub_get_state(evsub) != PJSIP_EVSUB_STATE_TERMINATED) {
+		return;
+	}
+
+
+	/* It's easier to write this as what we WANT to process, then negate it. */
+	if (!(sub_tree->state == SIP_SUB_TREE_TERMINATE_IN_PROGRESS
+		|| (event->type == PJSIP_EVENT_TSX_STATE && sub_tree->state == SIP_SUB_TREE_NORMAL)
+		)) {
+		ast_debug(3, "Do nothing.\n");
+		return;
+	}
+
+#ifdef HAVE_PJSIP_EVSUB_PENDING_NOTIFY
+	/* This check looks for re-subscribes with an expires of 0. If we receive one of those,
+	   we don't want to clean the evsub because we still need it to send the final NOTIFY.
+	   This was previously handled by pubsub_on_rx_refresh setting:
+	   'sub_tree->state = SIP_SUB_TREE_TERMINATE_PENDING' */
+	if (event->body.tsx_state.type == PJSIP_EVENT_RX_MSG &&
+	    !pjsip_method_cmp(&event->body.tsx_state.tsx->method, &pjsip_subscribe_method) &&
+	    pjsip_evsub_get_expires(evsub) == 0) {
+
+		ast_debug(3, "Subscription ending, do nothing.\n");
+		return;
+	}
+#endif
+	/* If we made it this far, we want to clean the sub tree. For pjproject <2.13, the sub_tree
+	   state check makes sure the evsub is not cleaned at the wrong time */
+	clean_sub_tree(evsub);
+}
+
 static int pubsub_on_refresh_timeout(void *userdata)
 {
 	struct sip_subscription_tree *sub_tree = userdata;
@@ -4065,14 +4118,22 @@ static int cmp_subscription_childrens(struct ast_sip_subscription *s1, struct as
 	return 0;
 }
 
+static int destroy_subscriptions_task(void *obj)
+{
+	struct ast_sip_subscription *sub = (struct ast_sip_subscription *) obj;
+
+	destroy_subscriptions(sub);
+
+	return 0;
+}
+
 /*!
  * \brief Called whenever an in-dialog SUBSCRIBE is received
  *
  * This includes both SUBSCRIBE requests that actually refresh the subscription
  * as well as SUBSCRIBE requests that end the subscription.
  *
- * In either case we push serialized_pubsub_on_refresh_timeout to send an
- * appropriate NOTIFY request.
+ * In either case we push an appropriate NOTIFY via pubsub_on_refresh_timeout.
  */
 static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata,
 		int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body)
@@ -4132,8 +4193,25 @@ static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata,
 						new_root->version = old_root->version;
 						sub_tree->root = new_root;
 						sub_tree->generate_initial_notify = 1;
+
+						/* If there is scheduled notification need to delete it to avoid use old subscriptions */
+						if (sub_tree->notify_sched_id > -1) {
+							AST_SCHED_DEL_UNREF(sched, sub_tree->notify_sched_id, ao2_ref(sub_tree, -1));
+							sub_tree->send_scheduled_notify = 0;
+						}
+
+						/* Terminate old subscriptions to stop sending NOTIFY messages on exten/device state changes */
+						set_state_terminated(old_root);
+
+						/* Shutdown old subscriptions to remove exten/device state change callbacks
+						 that can queue tasks for old subscriptions */
 						shutdown_subscriptions(old_root);
-						destroy_subscriptions(old_root);
+
+						/* Postpone destruction until all already queued tasks that may be using old subscriptions have completed */
+						if (ast_sip_push_task(sub_tree->serializer, destroy_subscriptions_task, old_root)) {
+							ast_log(LOG_ERROR, "Failed to push task to destroy old subscriptions for RLS '%s->%s'.\n",
+								ast_sorcery_object_get_id(endpoint), old_root->resource);
+						}
 					} else {
 						destroy_subscriptions(new_root);
 					}
@@ -4149,12 +4227,21 @@ static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata,
 
 	subscription_persistence_update(sub_tree, rdata, SUBSCRIPTION_PERSISTENCE_REFRESHED);
 
+#ifdef HAVE_PJSIP_EVSUB_PENDING_NOTIFY
+	/* As of pjsip 2.13, the NOTIFY has to be sent within this function as pjproject now
+	   requires it.  Previously this would have caused an early NOTIFY to go out before the
+	   SUBSCRIBE's 200 OK. The previous solution was to push the NOTIFY, but now pjproject
+	   looks for the NOTIFY to be sent from this function and caches it to send after it
+	   auto-replies to the SUBSCRIBE. */
+	pubsub_on_refresh_timeout(sub_tree);
+#else
 	if (ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_refresh_timeout, ao2_bump(sub_tree))) {
 		/* If we can't push the NOTIFY refreshing task...we'll just go with it. */
 		ast_log(LOG_ERROR, "Failed to push task to send NOTIFY.\n");
 		sub_tree->state = SIP_SUB_TREE_NORMAL;
 		ao2_ref(sub_tree, -1);
 	}
+#endif
 
 	if (sub_tree->is_list) {
 		pj_list_insert_before(res_hdr, create_require_eventlist(rdata->tp_info.pool));
@@ -4952,7 +5039,11 @@ static int persistence_expires_str2struct(const struct aco_option *opt, struct a
 static int persistence_expires_struct2str(const void *obj, const intptr_t *args, char **buf)
 {
 	const struct subscription_persistence *persistence = obj;
-	return (ast_asprintf(buf, "%ld", persistence->expires.tv_sec) < 0) ? -1 : 0;
+	char secs[AST_TIME_T_LEN];
+
+	ast_time_t_to_string(persistence->expires.tv_sec, secs, sizeof(secs));
+
+	return (ast_asprintf(buf, "%s", secs) < 0) ? -1 : 0;
 }
 
 #define RESOURCE_LIST_INIT_SIZE 4
@@ -5800,7 +5891,7 @@ static int load_module(void)
 	ast_sorcery_object_field_register(sorcery, "subscription_persistence", "src_port", "0", OPT_UINT_T, 0,
 		FLDSET(struct subscription_persistence, src_port));
 	ast_sorcery_object_field_register(sorcery, "subscription_persistence", "transport_key", "0", OPT_CHAR_ARRAY_T, 0,
-		CHARFLDSET(struct subscription_persistence, transport_key));
+		CHARFLDSET(struct subscription_persistence, transport_type));
 	ast_sorcery_object_field_register(sorcery, "subscription_persistence", "local_name", "", OPT_CHAR_ARRAY_T, 0,
 		CHARFLDSET(struct subscription_persistence, local_name));
 	ast_sorcery_object_field_register(sorcery, "subscription_persistence", "local_port", "0", OPT_UINT_T, 0,
diff --git a/res/res_pjsip_refer.c b/res/res_pjsip_refer.c
index 2c442426a9978d58b34c3112c6eac723db76d293..c61cf56bd5b4b1c6060abdfda855f73482b06fc0 100644
--- a/res/res_pjsip_refer.c
+++ b/res/res_pjsip_refer.c
@@ -989,6 +989,10 @@ static int refer_incoming_invite_request(struct ast_sip_session *session, struct
 	ast_debug(3, "INVITE with Replaces being attempted.  '%s' --> '%s'\n",
 		ast_channel_name(session->channel), ast_channel_name(invite.channel));
 
+	/* Unhold the channel now, as later we are not having access to it anymore */
+	ast_queue_unhold(session->channel);
+	ast_queue_frame(session->channel, &ast_null_frame);
+
 	if (!invite.bridge) {
 		struct ast_channel *chan = session->channel;
 
diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c
index 9c999c15cfe62451555e08728c45d26180214426..f2b785bab99b3a9899ed7ca52f8775386f031f93 100644
--- a/res/res_pjsip_registrar.c
+++ b/res/res_pjsip_registrar.c
@@ -1365,12 +1365,13 @@ static void *check_expiration_thread(void *data)
 {
 	struct ao2_container *contacts;
 	struct ast_variable *var;
-	char *time = alloca(64);
+	char time[AST_TIME_T_LEN];
 
 	while (check_interval) {
 		sleep(check_interval);
 
-		sprintf(time, "%ld", ast_tvnow().tv_sec);
+		ast_time_t_to_string(ast_tvnow().tv_sec, time, sizeof(time));
+
 		var = ast_variable_new("expiration_time <=", time, "");
 
 		ast_debug(4, "Woke up at %s  Interval: %d\n", time, check_interval);
diff --git a/res/res_pjsip_rfc3326.c b/res/res_pjsip_rfc3326.c
index 7d096c1f7dbdc81288c3cbfa4739112e7a52ac45..458b5e97d31ab10acbfd66532079e256caa71e67 100644
--- a/res/res_pjsip_rfc3326.c
+++ b/res/res_pjsip_rfc3326.c
@@ -42,6 +42,7 @@ static void rfc3326_use_reason_header(struct ast_sip_session *session, struct pj
 	char *cause;
 	char *text;
 	int code;
+	int cause_q850, cause_sip;
 
 	header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_reason, NULL);
 	for (; header;
@@ -49,21 +50,27 @@ static void rfc3326_use_reason_header(struct ast_sip_session *session, struct pj
 		ast_copy_pj_str(buf, &header->hvalue, sizeof(buf));
 		cause = ast_skip_blanks(buf);
 
-		if (strncasecmp(cause, "Q.850", 5) || !(cause = strstr(cause, "cause="))) {
+		cause_q850 = !strncasecmp(cause, "Q.850", 5);
+		cause_sip = !strncasecmp(cause, "SIP", 3);
+		if ((cause_q850 || cause_sip) && (cause = strstr(cause, "cause="))) {
+			/* If text is present get rid of it */
+			if ((text = strchr(cause, ';'))) {
+				*text = '\0';
+			}
+
+			if (sscanf(cause, "cause=%30d", &code) != 1) {
+				continue;
+			}
+		} else {
 			continue;
 		}
-
-		/* If text is present get rid of it */
-		if ((text = strstr(cause, ";"))) {
-			*text = '\0';
+		if (cause_q850) {
+			ast_channel_hangupcause_set(session->channel, code & 0x7f);
+			break;
+		} else if (cause_sip) {
+			ast_channel_hangupcause_set(session->channel, ast_sip_hangup_sip2cause(code));
+			break;
 		}
-
-		if (sscanf(cause, "cause=%30d", &code) != 1) {
-			continue;
-		}
-
-		ast_channel_hangupcause_set(session->channel, code & 0x7f);
-		break;
 	}
 }
 
diff --git a/res/res_pjsip_rfc3329.c b/res/res_pjsip_rfc3329.c
new file mode 100644
index 0000000000000000000000000000000000000000..f6faff2afe67e782ee4dbb4e119b8a8c33615423
--- /dev/null
+++ b/res/res_pjsip_rfc3329.c
@@ -0,0 +1,189 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Commend International
+ *
+ * Maximilian Fridrich <m.fridrich@commend.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_pjsip</depend>
+	<depend>res_pjsip_session</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/res_pjsip.h"
+#include "asterisk/res_pjsip_session.h"
+#include "asterisk/module.h"
+#include "asterisk/causes.h"
+#include "asterisk/threadpool.h"
+
+/*! \brief Private data structure used with the modules's datastore */
+struct rfc3329_store_data {
+	int last_rx_status_code;
+};
+
+static void datastore_destroy_cb(void *data)
+{
+	struct rfc3329_store_data *d = data;
+	if (d) {
+		ast_free(d);
+	}
+}
+
+/*! \brief The channel datastore the module uses to store state */
+static const struct ast_datastore_info rfc3329_store_datastore = {
+	.type = "rfc3329_store",
+	.destroy = datastore_destroy_cb
+};
+
+static void rfc3329_incoming_response(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+	RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, "rfc3329_store"), ao2_cleanup);
+	static const pj_str_t str_security_server = { "Security-Server", 15 };
+	struct ast_sip_contact_status *contact_status = NULL;
+	struct ast_sip_security_mechanism *mech;
+	struct rfc3329_store_data *store_data;
+	pjsip_generic_string_hdr *header;
+	char buf[128];
+	char *hdr_val;
+	char *mechanism;
+
+	if (!session || !session->endpoint || !session->endpoint->security_negotiation
+		|| !session->contact || !(contact_status = ast_sip_get_contact_status(session->contact))
+		|| !session->inv_session->dlg) {
+		return;
+	}
+
+	ao2_lock(contact_status);
+	if (AST_VECTOR_SIZE(&contact_status->security_mechanisms)) {
+		goto out;
+	}
+
+	if (!datastore
+		&& (datastore = ast_sip_session_alloc_datastore(&rfc3329_store_datastore, "rfc3329_store"))
+		&& (store_data = ast_calloc(1, sizeof(struct rfc3329_store_data)))) {
+
+		store_data->last_rx_status_code = rdata->msg_info.msg->line.status.code;
+		datastore->data = store_data;
+		ast_sip_session_add_datastore(session, datastore);
+	} else {
+		ast_log(AST_LOG_WARNING, "Could not store session data. Still attempting requests, but they might be missing necessary headers.\n");
+	}
+
+	header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_security_server, NULL);
+	for (; header;
+		header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_security_server, header->next)) {
+		/* Parse Security-Server headers and add to contact status to use for future requests. */
+		ast_copy_pj_str(buf, &header->hvalue, sizeof(buf));
+		hdr_val = ast_skip_blanks(buf);
+
+		while ((mechanism = ast_strsep(&hdr_val, ',', AST_STRSEP_ALL))) {
+			if (!ast_sip_str_to_security_mechanism(&mech, mechanism)) {
+				AST_VECTOR_APPEND(&contact_status->security_mechanisms, mech);
+			}
+		}
+	}
+
+out:
+	ao2_unlock(contact_status);
+	ao2_cleanup(contact_status);
+}
+
+static void add_outgoing_request_headers(struct ast_sip_endpoint *endpoint, struct ast_sip_contact *contact, struct pjsip_tx_data *tdata,
+	struct ast_datastore *datastore)
+{
+	static const pj_str_t security_verify = { "Security-Verify", 15 };
+	struct pjsip_generic_string_hdr *hdr = NULL;
+	struct ast_sip_contact_status *contact_status = NULL;
+	struct rfc3329_store_data *store_data;
+	
+	if (endpoint->security_negotiation != AST_SIP_SECURITY_NEG_MEDIASEC) {
+		return;
+	}
+
+	contact_status = ast_sip_get_contact_status(contact);
+	hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &security_verify, NULL);
+
+	if (contact_status == NULL) {
+		return;
+	}
+
+	ao2_lock(contact_status);
+	if (AST_VECTOR_SIZE(&contact_status->security_mechanisms) && hdr == NULL) {
+		/* Add Security-Verify headers (with q-value) */
+		ast_sip_add_security_headers(&contact_status->security_mechanisms, "Security-Verify", 0, tdata);
+	}
+	if (datastore) {
+		store_data = datastore->data;
+		if (store_data->last_rx_status_code == 401) {
+			/* Add Security-Client headers (no q-value) */
+			ast_sip_add_security_headers(&endpoint->security_mechanisms, "Security-Client", 0, tdata);
+		}
+	}
+	ao2_unlock(contact_status);
+
+	ao2_cleanup(contact_status);
+}
+
+static void rfc3329_outgoing_request(struct ast_sip_session *session, struct pjsip_tx_data *tdata)
+{
+	RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, "rfc3329_store"), ao2_cleanup);
+	if (session->contact == NULL) {
+		return;
+	}
+	add_outgoing_request_headers(session->endpoint, session->contact, tdata, datastore);
+}
+
+static struct ast_sip_session_supplement rfc3329_supplement = {
+	.incoming_response = rfc3329_incoming_response,
+	.outgoing_request = rfc3329_outgoing_request,
+};
+
+static void rfc3329_options_request(struct ast_sip_endpoint *endpoint, struct ast_sip_contact *contact, struct pjsip_tx_data *tdata)
+{
+	add_outgoing_request_headers(endpoint, contact, tdata, NULL);
+}
+
+static struct ast_sip_supplement rfc3329_options_supplement = {
+	.method = "OPTIONS",
+	.outgoing_request = rfc3329_options_request,
+};
+
+static int load_module(void)
+{
+	ast_sip_session_register_supplement(&rfc3329_supplement);
+	ast_sip_register_supplement(&rfc3329_options_supplement);
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_sip_session_unregister_supplement(&rfc3329_supplement);
+	ast_sip_unregister_supplement(&rfc3329_options_supplement);
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP RFC3329 Support (partial)",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.load_pri = AST_MODPRI_APP_DEPEND,
+	.requires = "res_pjsip,res_pjsip_session",
+);
diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c
index ffcf717c4ccb48781618bf59832a7d6aea35cd7d..f41e4906c1ffe85101f00adca99d787b239df850 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -107,9 +107,10 @@ static int rtp_check_timeout(const void *data)
 {
 	struct ast_sip_session_media *session_media = (struct ast_sip_session_media *)data;
 	struct ast_rtp_instance *rtp = session_media->rtp;
+	struct ast_channel *chan;
 	int elapsed;
+	int now;
 	int timeout;
-	struct ast_channel *chan;
 
 	if (!rtp) {
 		return 0;
@@ -120,41 +121,37 @@ static int rtp_check_timeout(const void *data)
 		return 0;
 	}
 
-	/* Get channel lock to make sure that we access a consistent set of values
-	 * (last_rx and direct_media_addr) - the lock is held when values are modified
-	 * (see send_direct_media_request()/check_for_rtp_changes() in chan_pjsip.c). We
-	 * are trying to avoid a situation where direct_media_addr has been reset but the
-	 * last-rx time was not set yet.
-	 */
-	ast_channel_lock(chan);
-
-	elapsed = time(NULL) - ast_rtp_instance_get_last_rx(rtp);
+	/* Store these values locally to avoid multiple function calls */
+	now = time(NULL);
 	timeout = ast_rtp_instance_get_timeout(rtp);
-	if (elapsed < timeout) {
-		ast_channel_unlock(chan);
+
+	/* If the channel is not in UP state or call is redirected
+	 * outside Asterisk return for later check.
+	 */
+	if (ast_channel_state(chan) != AST_STATE_UP || !ast_sockaddr_isnull(&session_media->direct_media_addr)) {
+		/* Avoiding immediately disconnect after channel up or direct media has been stopped */
+		ast_rtp_instance_set_last_rx(rtp, now);
 		ast_channel_unref(chan);
-		return (timeout - elapsed) * 1000;
+		/* Recheck after half timeout for avoiding possible races
+		* and faster reacting to cases while there is no an RTP at all.
+		*/
+		return timeout * 500;
 	}
 
-	/* Last RTP packet was received too long ago
-	 * - disconnect channel unless direct media is in use.
-	 */
-	if (!ast_sockaddr_isnull(&session_media->direct_media_addr)) {
-		ast_debug_rtp(3, "(%p) RTP not disconnecting channel '%s' for lack of %s RTP activity in %d seconds "
-			"since direct media is in use\n", rtp, ast_channel_name(chan),
-			ast_codec_media_type2str(session_media->type), elapsed);
-		ast_channel_unlock(chan);
+	elapsed = now - ast_rtp_instance_get_last_rx(rtp);
+	if (elapsed < timeout) {
 		ast_channel_unref(chan);
-		return timeout * 1000; /* recheck later, direct media may have ended then */
+		return (timeout - elapsed) * 1000;
 	}
 
 	ast_log(LOG_NOTICE, "Disconnecting channel '%s' for lack of %s RTP activity in %d seconds\n",
 		ast_channel_name(chan), ast_codec_media_type2str(session_media->type), elapsed);
 
+	ast_channel_lock(chan);
 	ast_channel_hangupcause_set(chan, AST_CAUSE_REQUESTED_CHAN_UNAVAIL);
-	ast_softhangup(chan, AST_SOFTHANGUP_DEV);
-
 	ast_channel_unlock(chan);
+
+	ast_softhangup(chan, AST_SOFTHANGUP_DEV);
 	ast_channel_unref(chan);
 
 	return 0;
@@ -1654,6 +1651,11 @@ static int add_crypto_to_stream(struct ast_sip_session *session,
 			media->attr[media->attr_count++] = attr;
 		} while ((tmp = AST_LIST_NEXT(tmp, sdp_srtp_list)));
 
+		if (session->endpoint->security_negotiation == AST_SIP_SECURITY_NEG_MEDIASEC) {
+			attr = pjmedia_sdp_attr_create(pool, "3ge2ae", &STR_MEDSECREQ);
+			media->attr[media->attr_count++] = attr;
+		}
+
 		break;
 	case AST_SIP_MEDIA_ENCRYPT_DTLS:
 		if (setup_dtls_srtp(session, session_media)) {
@@ -1919,6 +1921,16 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 			continue;
 		}
 
+		/* It is possible for some formats not to have SDP information available for them
+		 * and if this is the case, skip over them so the SDP can still be created.
+		 */
+		if (!ast_rtp_lookup_sample_rate2(1, format, 0)) {
+			ast_log(LOG_WARNING, "Format '%s' can not be added to SDP, consider disallowing it on endpoint '%s'\n",
+				ast_format_get_name(format), ast_sorcery_object_get_id(session->endpoint));
+			ao2_ref(format, -1);
+			continue;
+		}
+
 		/* If this stream is not a transport we need to use the transport codecs structure for payload management to prevent
 		 * conflicts.
 		 */
@@ -2270,15 +2282,14 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session,
 	 * instance itself.
 	 */
 	ast_rtp_instance_set_timeout(session_media->rtp, 0);
-	if (session->endpoint->media.rtp.timeout && !session_media->remotely_held) {
+	if (session->endpoint->media.rtp.timeout && !session_media->remotely_held && !session_media->locally_held) {
 		ast_rtp_instance_set_timeout(session_media->rtp, session->endpoint->media.rtp.timeout);
-	} else if (session->endpoint->media.rtp.timeout_hold && session_media->remotely_held) {
+	} else if (session->endpoint->media.rtp.timeout_hold && (session_media->remotely_held || session_media->locally_held)) {
 		ast_rtp_instance_set_timeout(session_media->rtp, session->endpoint->media.rtp.timeout_hold);
 	}
 
 	if (ast_rtp_instance_get_timeout(session_media->rtp)) {
-		session_media->timeout_sched_id = ast_sched_add_variable(sched,
-			ast_rtp_instance_get_timeout(session_media->rtp) * 1000, rtp_check_timeout,
+		session_media->timeout_sched_id = ast_sched_add_variable(sched,	500, rtp_check_timeout,
 			session_media, 1);
 	}
 
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index 80efa3221bb3c41f233a9365077bbffd08d3bfe6..d8df6b25162cfaf664377a592fc6bed079738b61 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -109,7 +109,7 @@ struct sdp_handler_list {
 	char stream_type[1];
 };
 
-static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer);
+static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer, const unsigned int ignore_active_stream_topology);
 
 static int sdp_handler_list_hash(const void *obj, int flags)
 {
@@ -1641,7 +1641,7 @@ static pjmedia_sdp_session *generate_session_refresh_sdp(struct ast_sip_session
 			pjmedia_sdp_neg_get_active_local(inv_session->neg, &previous_sdp);
 		}
 	}
-	SCOPE_EXIT_RTN_VALUE(create_local_sdp(inv_session, session, previous_sdp));
+	SCOPE_EXIT_RTN_VALUE(create_local_sdp(inv_session, session, previous_sdp, 0));
 }
 
 static void set_from_header(struct ast_sip_session *session)
@@ -1947,15 +1947,7 @@ static struct ast_sip_session_media_state *resolve_refresh_media_states(
 				/* All the same state, no need to update. */
 				SCOPE_EXIT_EXPR(continue, "%s: All in the same state so nothing to do\n", session_name);
 			}
-			if (da_state != ca_state) {
-				/*
-				 * Something set the CA state between the time this request was queued
-				 * and now.  The CA state wins so we don't do anything.
-				 */
-				SCOPE_EXIT_EXPR(continue, "%s: Ignoring request to change state from %s to %s\n",
-					session_name, ast_stream_state2str(ca_state), ast_stream_state2str(dp_state));
-			}
-			if (dp_state != da_state) {
+			if (dp_state != ca_state) {
 				/* DP needs to update the state */
 				ast_stream_set_state(np_stream, dp_state);
 				SCOPE_EXIT_EXPR(continue, "%s: Changed NP stream state from %s to %s\n",
@@ -2532,9 +2524,9 @@ static int sip_session_refresh(struct ast_sip_session *session,
 		ast_sip_add_header(tdata,"Security-Client","sdes-srtp;mediasec");
 		ast_sip_add_header(tdata,"Proxy-Require","mediasec");
 		ast_sip_add_header(tdata,"Require","mediasec");
-		if(!AST_LIST_EMPTY(&session->endpoint->security_mechanisms)) {
+		if(!AST_LIST_EMPTY(&session->endpoint->secur_mechanisms)) {
 			struct security_mechanism *sec_mechanism;
-			AST_LIST_TRAVERSE(&session->endpoint->security_mechanisms, sec_mechanism, entry) {
+			AST_LIST_TRAVERSE(&session->endpoint->secur_mechanisms, sec_mechanism, entry) {
 				ast_debug(3, "Adding security header: %s\n", sec_mechanism->value);
 				ast_sip_add_header(tdata,"Security-Verify",sec_mechanism->value);
 			}
@@ -2583,7 +2575,7 @@ int ast_sip_session_regenerate_answer(struct ast_sip_session *session,
 		pjmedia_sdp_neg_set_remote_offer(inv_session->pool, inv_session->neg, previous_offer);
 	}
 
-	new_answer = create_local_sdp(inv_session, session, previous_offer);
+	new_answer = create_local_sdp(inv_session, session, previous_offer, 0);
 	if (!new_answer) {
 		ast_log(LOG_WARNING, "Could not create a new local SDP answer for channel '%s'\n",
 			ast_channel_name(session->channel));
@@ -2603,7 +2595,13 @@ int ast_sip_session_regenerate_answer(struct ast_sip_session *session,
 
 void ast_sip_session_send_response(struct ast_sip_session *session, pjsip_tx_data *tdata)
 {
-	handle_outgoing_response(session, tdata);
+	pjsip_dialog *dlg = pjsip_tdata_get_dlg(tdata);
+	RAII_VAR(struct ast_sip_session *, dlg_session, dlg ? ast_sip_dialog_get_session(dlg) : NULL, ao2_cleanup);
+	if (!dlg_session) {
+		/* If the dialog has a session, handle_outgoing_response will be called
+		   from session_on_tx_response. If it does not, call it from here. */
+		handle_outgoing_response(session, tdata);
+	}
 	pjsip_inv_send_msg(session->inv_session, tdata);
 	return;
 }
@@ -2613,6 +2611,7 @@ static pj_bool_t session_on_rx_response(pjsip_rx_data *rdata);
 static void sipaddress_on_tx_request(pjsip_tx_data *tdata);
 static pj_status_t session_on_tx_request(pjsip_tx_data *tdata); // early media + SIPIPAddress
 static pj_status_t add_earlymedia_response_headers(pjsip_tx_data *tdata);
+static pj_status_t session_on_tx_response(pjsip_tx_data *tdata);
 static void session_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e);
 
 static pjsip_module session_module = {
@@ -2623,6 +2622,7 @@ static pjsip_module session_module = {
 	.on_tsx_state = session_on_tsx_state,
 	.on_tx_request = session_on_tx_request,
 	.on_tx_response = add_earlymedia_response_headers,
+	.on_tx_response = session_on_tx_response,
 };
 
 /*! \brief Determine whether the SDP provided requires deferral of negotiating or not
@@ -2824,6 +2824,7 @@ static pj_bool_t session_reinvite_on_rx_request(pjsip_rx_data *rdata)
 	}
 
 	pjsip_rx_data_clone(rdata, 0, &session->deferred_reinvite);
+
 	return PJ_TRUE;
 }
 
@@ -2884,7 +2885,7 @@ int ast_sip_session_create_invite(struct ast_sip_session *session, pjsip_tx_data
 	static const pj_str_t headerName = { "Content-Disposition", 19 };
 	SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
 
-	if (!(offer = create_local_sdp(session->inv_session, session, NULL))) {
+	if (!(offer = create_local_sdp(session->inv_session, session, NULL, 0))) {
 		pjsip_inv_terminate(session->inv_session, 500, PJ_FALSE);
 		SCOPE_EXIT_RTN_VALUE(-1, "Couldn't create offer\n");
 	}
@@ -2913,9 +2914,9 @@ int ast_sip_session_create_invite(struct ast_sip_session *session, pjsip_tx_data
 		ast_sip_add_header(*tdata,"Proxy-Require","mediasec");
 		ast_sip_add_header(*tdata,"Require","mediasec");
 
-		if(!AST_LIST_EMPTY(&session->endpoint->security_mechanisms)) {
+		if(!AST_LIST_EMPTY(&session->endpoint->secur_mechanisms)) {
 			struct security_mechanism *sec_mechanism;
-			AST_LIST_TRAVERSE(&session->endpoint->security_mechanisms, sec_mechanism, entry) {
+			AST_LIST_TRAVERSE(&session->endpoint->secur_mechanisms, sec_mechanism, entry) {
 				ast_debug(3, "Adding security header: %s\n", sec_mechanism->value);
 				ast_sip_add_header(*tdata,"Security-Verify",sec_mechanism->value);
 			}
@@ -3748,6 +3749,21 @@ struct ast_sip_session *ast_sip_dialog_get_session(pjsip_dialog *dlg)
 	return session;
 }
 
+/*! \brief Fetch just the Caller ID number in order of PAI, RPID, From */
+static int fetch_callerid_num(struct ast_sip_session *session, pjsip_rx_data *rdata, char *buf, size_t len)
+{
+	int res = -1;
+	struct ast_party_id id;
+
+	ast_party_id_init(&id);
+	if (!ast_sip_set_id_from_invite(rdata, &id, &session->endpoint->id.self, session->endpoint->id.trust_inbound)) {
+		ast_copy_string(buf, id.number.str, len);
+		res = 0;
+	}
+	ast_party_id_free(&id);
+	return res;
+}
+
 enum sip_get_destination_result {
 	/*! The extension was successfully found */
 	SIP_GET_DEST_EXTEN_FOUND,
@@ -3771,17 +3787,21 @@ enum sip_get_destination_result {
  */
 static enum sip_get_destination_result get_destination(struct ast_sip_session *session, pjsip_rx_data *rdata)
 {
+	char cid_num[AST_CHANNEL_NAME];
 	pjsip_uri *ruri = rdata->msg_info.msg->line.req.uri;
-	pjsip_sip_uri *sip_ruri;
 	struct ast_features_pickup_config *pickup_cfg;
 	const char *pickupexten;
 
-	if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) {
+	if (!ast_sip_is_allowed_uri(ruri)) {
 		return SIP_GET_DEST_UNSUPPORTED_URI;
 	}
 
-	sip_ruri = pjsip_uri_get_uri(ruri);
-	ast_copy_pj_str(session->exten, &sip_ruri->user, sizeof(session->exten));
+	ast_copy_pj_str(session->exten, ast_sip_pjsip_uri_get_username(ruri), sizeof(session->exten));
+	if (ast_strlen_zero(session->exten)) {
+		/* Some SIP devices send an empty extension for PLAR: this should map to s */
+		ast_debug(1, "RURI contains no user portion: defaulting to extension 's'\n");
+		ast_copy_string(session->exten, "s", sizeof(session->exten));
+	}
 
 	/*
 	 * We may want to match in the dialplan without any user
@@ -3799,8 +3819,12 @@ static enum sip_get_destination_result get_destination(struct ast_sip_session *s
 		ao2_ref(pickup_cfg, -1);
 	}
 
+	fetch_callerid_num(session, rdata, cid_num, sizeof(cid_num));
+
+	/* If there's an overlap_context override specified, use that; otherwise, just use the endpoint's context */
+
 	if (!strcmp(session->exten, pickupexten) ||
-		ast_exists_extension(NULL, session->endpoint->context, session->exten, 1, NULL)) {
+		ast_exists_extension(NULL, S_OR(session->endpoint->overlap_context, session->endpoint->context), session->exten, 1, S_OR(cid_num, NULL))) {
 		/*
 		 * Save off the INVITE Request-URI in case it is
 		 * needed: CHANNEL(pjsip,request_uri)
@@ -3815,7 +3839,7 @@ static enum sip_get_destination_result get_destination(struct ast_sip_session *s
 	 */
 	if (session->endpoint->allow_overlap && (
 		!strncmp(session->exten, pickupexten, strlen(session->exten)) ||
-		ast_canmatch_extension(NULL, session->endpoint->context, session->exten, 1, NULL))) {
+		ast_canmatch_extension(NULL, S_OR(session->endpoint->overlap_context, session->endpoint->context), session->exten, 1, S_OR(cid_num, NULL)))) {
 		/* Overlap partial match */
 		return SIP_GET_DEST_EXTEN_PARTIAL;
 	}
@@ -3898,8 +3922,23 @@ static pjsip_inv_session *pre_session_setup(pjsip_rx_data *rdata, const struct a
 	pjsip_dialog *dlg;
 	pjsip_inv_session *inv_session;
 	unsigned int options = endpoint->extensions.flags;
+	const pj_str_t STR_100REL = { "100rel", 6};
+	unsigned int i;
 	pj_status_t dlg_status = PJ_EUNKNOWN;
 
+	/*
+	 * If 100rel is set to "peer_supported" on the endpoint and the peer indicated support for 100rel
+	 * in the Supported header, send 1xx responses reliably by adding PJSIP_INV_REQUIRE_100REL to pjsip_inv_options flags.
+	 */
+	if (endpoint->rel100 == AST_SIP_100REL_PEER_SUPPORTED && rdata->msg_info.supported != NULL) {
+		for (i = 0; i < rdata->msg_info.supported->count; ++i) {
+			if (pj_stricmp(&rdata->msg_info.supported->values[i], &STR_100REL) == 0) {
+				options |= PJSIP_INV_REQUIRE_100REL;
+				break;
+			}
+		}
+	}
+
 	if (pjsip_inv_verify_request(rdata, &options, NULL, NULL, ast_sip_get_pjsip_endpoint(), &tdata) != PJ_SUCCESS) {
 		if (tdata) {
 			if (pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL) != PJ_SUCCESS) {
@@ -4127,10 +4166,10 @@ static int new_invite(struct new_invite *invite)
 			goto end;
 		}
 		/* We are creating a local SDP which is an answer to their offer */
-		local = create_local_sdp(invite->session->inv_session, invite->session, sdp_info->sdp);
+		local = create_local_sdp(invite->session->inv_session, invite->session, sdp_info->sdp, 0);
 	} else {
 		/* We are creating a local SDP which is an offer */
-		local = create_local_sdp(invite->session->inv_session, invite->session, NULL);
+		local = create_local_sdp(invite->session->inv_session, invite->session, NULL, 0);
 	}
 
 	/* If we were unable to create a local SDP terminate the session early, it won't go anywhere */
@@ -4506,6 +4545,18 @@ static pj_bool_t session_on_rx_request(pjsip_rx_data *rdata)
 		handled == PJ_TRUE ? "yes" : "no");
 }
 
+
+static pj_bool_t session_on_tx_response(pjsip_tx_data *tdata)
+{
+	pjsip_dialog *dlg = pjsip_tdata_get_dlg(tdata);
+	RAII_VAR(struct ast_sip_session *, session, dlg ? ast_sip_dialog_get_session(dlg) : NULL, ao2_cleanup);
+	if (session) {
+		handle_outgoing_response(session, tdata);
+	}
+
+	return PJ_SUCCESS;
+}
+
 static void resend_reinvite(pj_timer_heap_t *timer, pj_timer_entry *entry)
 {
 	struct ast_sip_session *session = entry->user_data;
@@ -4688,6 +4739,7 @@ static void handle_session_begin(struct ast_sip_session *session)
 static void handle_session_destroy(struct ast_sip_session *session)
 {
 	struct ast_sip_session_supplement *iter;
+
 	AST_LIST_TRAVERSE(&session->supplements, iter, next) {
 		if (iter->session_destroy) {
 			iter->session_destroy(session);
@@ -4777,7 +4829,6 @@ static void handle_outgoing_response(struct ast_sip_session *session, pjsip_tx_d
 {
 	struct ast_sip_session_supplement *supplement;
 	struct pjsip_status_line status = tdata->msg->line.status;
-
 	pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
 	SCOPE_ENTER(3, "%s: Method is %.*s, Response is %d %.*s\n", ast_sip_session_get_name(session),
 		(int) pj_strlen(&cseq->method.name),
@@ -5085,7 +5136,8 @@ static void session_inv_on_tsx_state_changed(pjsip_inv_session *inv, pjsip_trans
 						ast_debug(1, "%s: reINVITE received final response code %d\n",
 							ast_sip_session_get_name(session),
 							tsx->status_code);
-						if ((tsx->status_code == 401 || tsx->status_code == 407)
+						if ((tsx->status_code == 401 || tsx->status_code == 407
+							|| (session->endpoint->security_negotiation && tsx->status_code == 494))
 							&& ++session->authentication_challenge_count < MAX_RX_CHALLENGES
 							&& !ast_sip_create_request_with_auth(
 								&session->endpoint->outbound_auths,
@@ -5179,7 +5231,7 @@ static void session_inv_on_tsx_state_changed(pjsip_inv_session *inv, pjsip_trans
 						ast_sip_session_get_name(session),
 						(int) pj_strlen(&tsx->method.name), pj_strbuf(&tsx->method.name),
 						tsx->status_code);
-					if ((tsx->status_code == 401 || tsx->status_code == 407)
+					if ((tsx->status_code == 401 || tsx->status_code == 407 || tsx->status_code == 494)
 						&& ++session->authentication_challenge_count < MAX_RX_CHALLENGES
 						&& !ast_sip_create_request_with_auth(
 							&session->endpoint->outbound_auths,
@@ -5371,7 +5423,7 @@ static int add_bundle_groups(struct ast_sip_session *session, pj_pool_t *pool, p
 	return 0;
 }
 
-static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer)
+static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer, const unsigned int ignore_active_stream_topology)
 {
 	static const pj_str_t STR_IN = { "IN", 2 };
 	static const pj_str_t STR_IP4 = { "IP4", 3 };
@@ -5404,11 +5456,19 @@ static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, stru
 		/* We've encountered a situation where we have been told to create a local SDP but noone has given us any indication
 		 * of what kind of stream topology they would like. We try to not alter the current state of the SDP negotiation
 		 * by using what is currently negotiated. If this is unavailable we fall back to what is configured on the endpoint.
+		 * We will also do this if wanted by the ignore_active_stream_topology flag.
 		 */
+		ast_trace(-1, "no information about stream topology received\n");
 		ast_stream_topology_free(session->pending_media_state->topology);
-		if (session->active_media_state->topology) {
+		if (session->active_media_state->topology && !ignore_active_stream_topology) {
+			ast_trace(-1, "using existing topology\n");
 			session->pending_media_state->topology = ast_stream_topology_clone(session->active_media_state->topology);
 		} else {
+			if (ignore_active_stream_topology) {
+				ast_trace(-1, "fall back to endpoint configuration - ignore active stream topolog\n");
+			} else {
+				ast_trace(-1, "fall back to endpoint configuration\n");
+			}
 			session->pending_media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology);
 		}
 		if (!session->pending_media_state->topology) {
@@ -5546,7 +5606,7 @@ static void session_inv_on_rx_offer(pjsip_inv_session *inv, const pjmedia_sdp_se
 		SCOPE_EXIT_RTN("%s: handle_incoming_sdp failed\n", ast_sip_session_get_name(session));
 	}
 
-	if ((answer = create_local_sdp(inv, session, offer))) {
+	if ((answer = create_local_sdp(inv, session, offer, 0))) {
 		pjsip_inv_set_sdp_answer(inv, answer);
 		SCOPE_EXIT_RTN("%s: Set SDP answer\n", ast_sip_session_get_name(session));
 	}
@@ -5559,6 +5619,7 @@ static void session_inv_on_create_offer(pjsip_inv_session *inv, pjmedia_sdp_sess
 	const pjmedia_sdp_session *previous_sdp = NULL;
 	pjmedia_sdp_session *offer;
 	int i;
+	unsigned int ignore_active_stream_topology = 0;
 
 	/* We allow PJSIP to produce an SDP if no channel is present. This may result
 	 * in an incorrect SDP occurring, but if no channel is present then we are in
@@ -5566,8 +5627,26 @@ static void session_inv_on_create_offer(pjsip_inv_session *inv, pjmedia_sdp_sess
 	 * produce an SDP doesn't need to worry about a channel being present or not,
 	 * just in case.
 	 */
+	SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session));
 	if (!session->channel) {
-		return;
+		SCOPE_EXIT_RTN("%s: No channel\n", ast_sip_session_get_name(session));
+	}
+
+	/* Some devices send a re-INVITE offer with empty SDP. Asterisk by default return
+	 * an answer with the current used codecs, which is not strictly compliant to RFC
+	 * 3261 (SHOULD requirement). So we detect this condition and include all
+	 * configured codecs in the answer if the workaround is activated. The actual
+	 * logic is in the create_local_sdp function. We can't detect here that we have
+	 * no SDP body in the INVITE, as we don't have access to the message.
+	 */
+	if (inv->invite_tsx && inv->state == PJSIP_INV_STATE_CONFIRMED
+			&& inv->invite_tsx->method.id == PJSIP_INVITE_METHOD) {
+		ast_trace(-1, "re-INVITE\n");
+		if (inv->invite_tsx->role == PJSIP_ROLE_UAS
+				&& ast_sip_get_all_codecs_on_empty_reinvite()) {
+			ast_trace(-1, "UAS role, include all codecs in the answer on empty SDP\n");
+			ignore_active_stream_topology = 1;
+		}
 	}
 
 	if (inv->neg) {
@@ -5578,9 +5657,13 @@ static void session_inv_on_create_offer(pjsip_inv_session *inv, pjmedia_sdp_sess
 		}
 	}
 
-	offer = create_local_sdp(inv, session, previous_sdp);
+	if (ignore_active_stream_topology) {
+		offer = create_local_sdp(inv, session, NULL, 1);
+	} else {
+		offer = create_local_sdp(inv, session, previous_sdp, 0);
+	}
 	if (!offer) {
-		return;
+		SCOPE_EXIT_RTN("%s: create offer failed\n", ast_sip_session_get_name(session));
 	}
 
 	ast_queue_unhold(session->channel);
@@ -5626,6 +5709,7 @@ static void session_inv_on_create_offer(pjsip_inv_session *inv, pjmedia_sdp_sess
 	}
 
 	*p_offer = offer;
+	SCOPE_EXIT_RTN("%s: offer created\n", ast_sip_session_get_name(session));
 }
 
 static void session_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status)
@@ -6121,6 +6205,7 @@ AST_TEST_DEFINE(test_resolve_refresh_media_states)
 
 	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
 	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
 	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
 	CHECKER();
 
@@ -6159,8 +6244,9 @@ AST_TEST_DEFINE(test_resolve_refresh_media_states)
 
 	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
 	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
-	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
 	test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
 	CHECKER();
 
 	RESET_STATE(7);
@@ -6206,6 +6292,7 @@ AST_TEST_DEFINE(test_resolve_refresh_media_states)
 
 	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
 	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
 	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
 	test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
 	CHECKER();
@@ -6226,6 +6313,8 @@ AST_TEST_DEFINE(test_resolve_refresh_media_states)
 	test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1);
 
 	test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
+	test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
 	test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
 	test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1);
 	CHECKER();
diff --git a/res/res_pjsip_stir_shaken.c b/res/res_pjsip_stir_shaken.c
index 19e846ff73d4bdd156fe91d7e19ba2d6d108aa8e..1089e60a7c476b00301974b519f48f3e5df2d488 100644
--- a/res/res_pjsip_stir_shaken.c
+++ b/res/res_pjsip_stir_shaken.c
@@ -225,8 +225,13 @@ static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_r
 	}
 
 	profile = ast_stir_shaken_get_profile(session->endpoint->stir_shaken_profile);
+	/* Profile should be checked first as it takes priority over anything else.
+	 * If there is a profile and it doesn't have verification enabled, do nothing.
+	 * If there is no profile and the stir_shaken option is either not set or does
+	 * not support verification, do nothing.
+	 */
 	if ((profile && !ast_stir_shaken_profile_supports_verification(profile))
-		&& ((session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_VERIFY) == 0)) {
+		|| (!profile && (session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_VERIFY) == 0)) {
 		return 0;
 	}
 
@@ -394,7 +399,22 @@ static int add_identity_header(const struct ast_sip_session *session, pjsip_tx_d
 		return -1;
 	}
 
-	ast_copy_pj_str(dest_tn, &uri->user, uri->user.slen + 1);
+	/* Remove everything except 0-9, *, and # in telephone number according to RFC 8224
+	 * (required by RFC 8225 as part of canonicalization) */
+	{
+		int i;
+		const char *s = uri->user.ptr;
+		char *new_tn = dest_tn;
+		/* We're only removing characters, if anything, so the buffer is guaranteed to be large enough */
+		for (i = 0; i < uri->user.slen; i++) {
+			if (isdigit(*s) || *s == '#' || *s == '*') { /* Only characters allowed */
+				*new_tn++ = *s;
+			}
+			s++;
+		}
+		*new_tn = '\0';
+		ast_debug(4, "Canonicalized telephone number %.*s -> %s\n", (int) uri->user.slen, uri->user.ptr, dest_tn);
+	}
 
 	/* x5u (public key URL), attestation, and origid will be added by ast_stir_shaken_sign */
 	json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: [s]}, s: {s: s}}}",
@@ -422,7 +442,9 @@ static int add_identity_header(const struct ast_sip_session *session, pjsip_tx_d
 	}
 
 	payload = ast_json_object_get(json, "payload");
-	dumped_string = ast_json_dump_string(payload);
+	/* Fields must appear in lexiographic order: https://www.rfc-editor.org/rfc/rfc8588.html#section-6
+	 * https://www.rfc-editor.org/rfc/rfc8225.html#section-9 */
+	dumped_string = ast_json_dump_string_sorted(payload);
 	encoded_payload = ast_base64url_encode_string(dumped_string);
 	ast_json_free(dumped_string);
 	if (!encoded_payload) {
@@ -478,8 +500,13 @@ static void stir_shaken_outgoing_request(struct ast_sip_session *session, pjsip_
 	RAII_VAR(struct stir_shaken_profile *, profile, NULL, ao2_cleanup);
 
 	profile = ast_stir_shaken_get_profile(session->endpoint->stir_shaken_profile);
+	/* Profile should be checked first as it takes priority over anything else.
+	 * If there is a profile and it doesn't have attestation enabled, do nothing.
+	 * If there is no profile and the stir_shaken option is either not set or does
+	 * not support attestation, do nothing.
+	 */
 	if ((profile && !ast_stir_shaken_profile_supports_attestation(profile))
-		&& ((session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_ATTEST) == 0)) {
+		|| (!profile && (session->endpoint->stir_shaken & AST_SIP_STIR_SHAKEN_ATTEST) == 0)) {
 		return;
 	}
 
diff --git a/res/res_pjsip_transport_websocket.c b/res/res_pjsip_transport_websocket.c
index 1b882dac14bd90faee172c51c7cf0affe293a284..3772097d3d9cfff7a308f5c10fe669b14771ac3c 100644
--- a/res/res_pjsip_transport_websocket.c
+++ b/res/res_pjsip_transport_websocket.c
@@ -225,6 +225,8 @@ static int transport_create(void *data)
 	pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&buf, ws_addr_str), &newtransport->transport.local_addr);
 	pj_strdup2(pool, &newtransport->transport.local_name.host, ast_sockaddr_stringify_addr(ast_websocket_local_address(newtransport->ws_session)));
 	newtransport->transport.local_name.port = ast_sockaddr_port(ast_websocket_local_address(newtransport->ws_session));
+	pj_strdup2(pool, &newtransport->transport.remote_name.host, ast_sockaddr_stringify_addr(ast_websocket_remote_address(newtransport->ws_session)));
+	newtransport->transport.remote_name.port = ast_sockaddr_port(ast_websocket_remote_address(newtransport->ws_session));
 
 	newtransport->transport.flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)newtransport->transport.key.type);
 	newtransport->transport.dir = PJSIP_TP_DIR_INCOMING;
@@ -415,6 +417,32 @@ static void websocket_cb(struct ast_websocket *session, struct ast_variable *par
 	ast_websocket_unref(session);
 }
 
+static void save_orig_contact_host(pjsip_rx_data *rdata, pjsip_sip_uri *uri)
+{
+	pjsip_param *x_orig_host;
+	pj_str_t p_value;
+#define COLON_LEN 1
+#define MAX_PORT_LEN 5
+
+	if (rdata->msg_info.msg->type != PJSIP_REQUEST_MSG ||
+		rdata->msg_info.msg->line.req.method.id != PJSIP_REGISTER_METHOD) {
+		return;
+	}
+
+	ast_debug(1, "Saving contact '%.*s:%d'\n",
+		(int)uri->host.slen, uri->host.ptr, uri->port);
+
+	x_orig_host = PJ_POOL_ALLOC_T(rdata->tp_info.pool, pjsip_param);
+	x_orig_host->name = pj_strdup3(rdata->tp_info.pool, "x-ast-orig-host");
+	p_value.slen = pj_strlen(&uri->host) + COLON_LEN + MAX_PORT_LEN;
+	p_value.ptr = (char*)pj_pool_alloc(rdata->tp_info.pool, p_value.slen + 1);
+	p_value.slen = snprintf(p_value.ptr, p_value.slen + 1, "%.*s:%d", (int)uri->host.slen, uri->host.ptr, uri->port);
+	pj_strassign(&x_orig_host->value, &p_value);
+	pj_list_insert_before(&uri->other_param, x_orig_host);
+
+	return;
+}
+
 /*!
  * \brief Store the transport a message came in on, so it can be used for outbound messages to that contact.
  */
@@ -436,6 +464,10 @@ static pj_bool_t websocket_on_rx_msg(pjsip_rx_data *rdata)
 		pjsip_sip_uri *uri = pjsip_uri_get_uri(contact->uri);
 		const pj_str_t *txp_str = &STR_WS;
 
+		/* Saving the contact on REGISTER so it can be restored on outbound response
+		 * This will actually be done by restore_orig_contact_host in res_pjsip_nat, via nat_on_tx_message */
+		save_orig_contact_host(rdata, uri);
+
 		if (DEBUG_ATLEAST(4)) {
 			char src_addr_buffer[AST_SOCKADDR_BUFLEN];
 			const char *ipv6_s = "", *ipv6_e = "";
diff --git a/res/res_pktccops.c b/res/res_pktccops.c
index 76344068c62b45ecf1d054963858e19cd897548d..6b6ad332f8f47d276f4a2711ccf1357b2c2bd2aa 100644
--- a/res/res_pktccops.c
+++ b/res/res_pktccops.c
@@ -32,7 +32,7 @@
 
 /*** MODULEINFO
         <defaultenabled>no</defaultenabled>
-	<support_level>extended</support_level>
+	<support_level>deprecated</support_level>
 	<deprecated_in>19</deprecated_in>
 	<removed_in>21</removed_in>
  ***/
@@ -1479,7 +1479,7 @@ static int reload_module(void)
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "PktcCOPS manager for MGCP",
-	.support_level = AST_MODULE_SUPPORT_EXTENDED,
+	.support_level = AST_MODULE_SUPPORT_DEPRECATED,
 	.load = load_module,
 	.unload = unload_module,
 	.reload = reload_module,
diff --git a/res/res_prometheus.c b/res/res_prometheus.c
index 4aacf6e85ce4b637fab77419a35993bfb6d1c938..9e4618132eb12019a6dffeba390cb9620c3ca66f 100644
--- a/res/res_prometheus.c
+++ b/res/res_prometheus.c
@@ -974,8 +974,13 @@ static int load_module(void)
 	if (cli_init()
 		|| channel_metrics_init()
 		|| endpoint_metrics_init()
-		|| bridge_metrics_init()
-		|| pjsip_outbound_registration_metrics_init()) {
+		|| bridge_metrics_init()) {
+		goto cleanup;
+	}
+
+	if(ast_module_check("res_pjsip_outbound_registration.so")) {
+		/* Call a local function, used in the core prometheus code only */
+		if (pjsip_outbound_registration_metrics_init())
 		goto cleanup;
 	}
 
@@ -1004,6 +1009,7 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_
 	.load_pri = AST_MODPRI_DEFAULT,
 #ifdef HAVE_PJPROJECT
 	/* This module explicitly calls into res_pjsip if Asterisk is built with PJSIP support, so they are required. */
-	.requires = "res_pjsip,res_pjsip_outbound_registration",
+	.requires = "res_pjsip",
+	.optional_modules = "res_pjsip_outbound_registration",
 #endif
 );
diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index a65458b7bd7a63eddc7abe469a3d7e94dc3fcc1d..c245064bac0df579fafa390fcc92a038434beb98 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -47,6 +47,7 @@
 #include <math.h>
 
 #ifdef HAVE_OPENSSL
+#define OPENSSL_SUPPRESS_DEPRECATED 1
 #include <openssl/opensslconf.h>
 #include <openssl/opensslv.h>
 #if !defined(OPENSSL_NO_SRTP) && (OPENSSL_VERSION_NUMBER >= 0x10001000L)
@@ -195,6 +196,16 @@ enum strict_rtp_mode {
 #define DEFAULT_STUN_SOFTWARE_ATTRIBUTE 1
 #define DEFAULT_DTLS_MTU 1200
 
+/*!
+ * Because both ends usually don't start sending RTP
+ * at the same time, some of the calculations like
+ * rtt and jitter will probably be unstable for a while
+ * so we'll skip some received packets before starting
+ * analyzing.  This just affects analyzing; we still
+ * process the RTP as normal.
+ */
+#define RTP_IGNORE_FIRST_PACKETS_COUNT 15
+
 extern struct ast_srtp_res *res_srtp;
 extern struct ast_srtp_policy_res *res_srtp_policy;
 
@@ -392,22 +403,32 @@ struct ast_rtp {
 	unsigned int lastovidtimestamp;
 	unsigned int lastitexttimestamp;
 	unsigned int lastotexttimestamp;
+	int prevrxseqno;                /*!< Previous received packeted sequence number, from the network */
 	int lastrxseqno;                /*!< Last received sequence number, from the network */
-	int expectedrxseqno;		/*!< Next expected sequence number, from the network */
+	int expectedrxseqno;            /*!< Next expected sequence number, from the network */
 	AST_VECTOR(, int) missing_seqno; /*!< A vector of sequence numbers we never received */
 	int expectedseqno;		/*!< Next expected sequence number, from the core */
 	unsigned short seedrxseqno;     /*!< What sequence number did they start with?*/
-	unsigned int seedrxts;          /*!< What RTP timestamp did they start with? */
 	unsigned int rxcount;           /*!< How many packets have we received? */
 	unsigned int rxoctetcount;      /*!< How many octets have we received? should be rxcount *160*/
 	unsigned int txcount;           /*!< How many packets have we sent? */
 	unsigned int txoctetcount;      /*!< How many octets have we sent? (txcount*160)*/
 	unsigned int cycles;            /*!< Shifted count of sequence number cycles */
-	double rxjitter;                /*!< Interarrival jitter at the moment in seconds to be reported */
-	double rxtransit;               /*!< Relative transit time for previous packet */
 	struct ast_format *lasttxformat;
 	struct ast_format *lastrxformat;
 
+	/*
+	 * RX RTP Timestamp and Jitter calculation.
+	 */
+	double rxstart;                       /*!< RX time of the first packet in the session in seconds since EPOCH. */
+	double rxstart_stable;                /*!< RX time of the first packet after RTP_IGNORE_FIRST_PACKETS_COUNT */
+	unsigned int remote_seed_rx_rtp_ts;         /*!< RTP timestamp of first RX packet. */
+	unsigned int remote_seed_rx_rtp_ts_stable;  /*!< RTP timestamp of first packet after RTP_IGNORE_FIRST_PACKETS_COUNT */
+	unsigned int last_transit_time_samples;     /*!< The last transit time in samples */
+	double rxjitter;                      /*!< Last calculated Interarrival jitter in seconds. */
+	double rxjitter_samples;              /*!< Last calculated Interarrival jitter in samples. */
+	double rxmes;                         /*!< Media Experince Score at the moment to be reported */
+
 	/* DTMF Reception Variables */
 	char resp;                        /*!< The current digit being processed */
 	unsigned int last_seqno;          /*!< The last known sequence number for any DTMF packet */
@@ -425,7 +446,7 @@ struct ast_rtp {
 	unsigned int flags;
 	struct timeval rxcore;
 	struct timeval txcore;
-	double drxcore;                 /*!< The double representation of the first received packet */
+
 	struct timeval dtmfmute;
 	struct ast_smoother *smoother;
 	unsigned short seqno;		/*!< Sequence number, RFC 3550, page 13. */
@@ -434,6 +455,12 @@ struct ast_rtp {
 	unsigned int asymmetric_codec;  /*!< Indicate if asymmetric send/receive codecs are allowed */
 
 	struct ast_rtp_instance *bundled; /*!< The RTP instance we are bundled to */
+	/*!
+	 * \brief The RTP instance owning us (used for debugging purposes)
+	 * We don't hold a reference to the instance because it created
+	 * us in the first place.  It can't go away.
+	 */
+	struct ast_rtp_instance *owner;
 	int stream_num; /*!< Stream num for this RTP instance */
 	AST_VECTOR(, struct rtp_ssrc_mapping) ssrc_mapping; /*!< Mappings of SSRC to RTP instances */
 	struct ast_sockaddr bind_address; /*!< Requested bind address for the sockets */
@@ -527,7 +554,7 @@ struct ast_rtcp {
 	unsigned int lastsrtxcount;     /*!< Transmit packet count when last SR sent */
 	double accumulated_transit;	/*!< accumulated a-dlsr-lsr */
 	double rtt;			/*!< Last reported rtt */
-	unsigned int reported_jitter;	/*!< The contents of their last jitter entry in the RR */
+	double reported_jitter;	/*!< The contents of their last jitter entry in the RR in seconds */
 	unsigned int reported_lost;	/*!< Reported lost packets in their RR */
 
 	double reported_maxjitter; /*!< Maximum reported interarrival jitter */
@@ -561,6 +588,19 @@ struct ast_rtcp {
 	double stdevrtt; /*!< Standard deviation of calculated round trip time */
 	unsigned int rtt_count; /*!< Calculated round trip time count */
 
+	double reported_mes;	/*!< The calculated MES from their last RR */
+	double reported_maxmes; /*!< Maximum reported mes */
+	double reported_minmes; /*!< Minimum reported mes */
+	double reported_normdev_mes; /*!< Mean of reported mes */
+	double reported_stdev_mes; /*!< Standard deviation of reported mes */
+	unsigned int reported_mes_count; /*!< Reported mes count */
+
+	double maxrxmes; /*!< Maximum of calculated mes */
+	double minrxmes; /*!< Minimum of calculated mes */
+	double normdev_rxmes; /*!< Mean of calculated mes */
+	double stdev_rxmes; /*!< Standard deviation of calculated mes */
+	unsigned int rxmes_count; /*!< mes count */
+
 	/* VP8: sequence number for the RTCP FIR FCI */
 	int firseq;
 
@@ -631,6 +671,8 @@ static void ast_rtp_set_remote_ssrc(struct ast_rtp_instance *instance, unsigned
 static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream_num);
 static int ast_rtp_extension_enable(struct ast_rtp_instance *instance, enum ast_rtp_extension extension);
 static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent);
+static void update_reported_mes_stats(struct ast_rtp *rtp);
+static void update_local_mes_stats(struct ast_rtp *rtp);
 
 #if defined(HAVE_OPENSSL) && (OPENSSL_VERSION_NUMBER >= 0x10001000L) && !defined(OPENSSL_NO_SRTP)
 static int ast_rtp_activate(struct ast_rtp_instance *instance);
@@ -4025,16 +4067,16 @@ static int ast_rtp_new(struct ast_rtp_instance *instance,
 	if (!(rtp = ast_calloc(1, sizeof(*rtp)))) {
 		return -1;
 	}
-
+	rtp->owner = instance;
 	/* Set default parameters on the newly created RTP structure */
 	rtp->ssrc = ast_random();
 	ast_uuid_generate_str(rtp->cname, sizeof(rtp->cname));
 	rtp->seqno = ast_random() & 0x7fff;
 	rtp->expectedrxseqno = -1;
 	rtp->expectedseqno = -1;
+	rtp->rxstart = -1;
 	rtp->sched = sched;
 	ast_sockaddr_copy(&rtp->bind_address, addr);
-
 	/* Transport creation operations can grab the RTP data from the instance, so set it */
 	ast_rtp_instance_set_data(instance, rtp);
 
@@ -4136,6 +4178,7 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
 	AST_VECTOR_FREE(&rtp->missing_seqno);
 
 	/* Finally destroy ourselves */
+	rtp->owner = NULL;
 	ast_free(rtp);
 
 	return 0;
@@ -4547,6 +4590,11 @@ static int ast_rtcp_generate_report(struct ast_rtp_instance *instance, unsigned
 
 	/* Compute statistics */
 	calculate_lost_packet_statistics(rtp, &lost_packets, &fraction_lost);
+	/*
+	 * update_local_mes_stats must be called AFTER
+	 * calculate_lost_packet_statistics
+	 */
+	update_local_mes_stats(rtp);
 
 	gettimeofday(&now, NULL);
 	rtcp_report->reception_report_count = rtp->themssrc_valid ? 1 : 0;
@@ -4570,7 +4618,7 @@ static int ast_rtcp_generate_report(struct ast_rtp_instance *instance, unsigned
 		report_block->lost_count.fraction = (fraction_lost & 0xff);
 		report_block->lost_count.packets = (lost_packets & 0xffffff);
 		report_block->highest_seq_no = (rtp->cycles | (rtp->lastrxseqno & 0xffff));
-		report_block->ia_jitter = (unsigned int)(rtp->rxjitter * ast_rtp_get_rate(rtp->f.subclass.format));
+		report_block->ia_jitter = (unsigned int)rtp->rxjitter_samples;
 		report_block->lsr = rtp->rtcp->themrxlsr;
 		/* If we haven't received an SR report, DLSR should be 0 */
 		if (!ast_tvzero(rtp->rtcp->rxlsr)) {
@@ -4647,20 +4695,24 @@ static int ast_rtcp_calculate_sr_rr_statistics(struct ast_rtp_instance *instance
 			ast_verbose("  Sent octets: %u\n", rtcp_report->sender_information.octet_count);
 		}
 		if (report_block) {
+			int rate = ast_rtp_get_rate(rtp->f.subclass.format);
 			ast_verbose("  Report block:\n");
 			ast_verbose("    Their SSRC: %u\n", report_block->source_ssrc);
 			ast_verbose("    Fraction lost: %d\n", report_block->lost_count.fraction);
 			ast_verbose("    Cumulative loss: %u\n", report_block->lost_count.packets);
 			ast_verbose("    Highest seq no: %u\n", report_block->highest_seq_no);
-			ast_verbose("    IA jitter: %.4f\n", (double)report_block->ia_jitter / ast_rtp_get_rate(rtp->f.subclass.format));
+			ast_verbose("    IA jitter (samp): %u\n", report_block->ia_jitter);
+			ast_verbose("    IA jitter (secs): %.6f\n", ast_samp2sec(report_block->ia_jitter, rate));
 			ast_verbose("    Their last SR: %u\n", report_block->lsr);
 			ast_verbose("    DLSR: %4.4f (sec)\n\n", (double)(report_block->dlsr / 65536.0));
 		}
 	}
 
-	message_blob = ast_json_pack("{s: s, s: s}",
+	message_blob = ast_json_pack("{s: s, s: s, s: f}",
 			"to", ast_sockaddr_stringify(&remote_address),
-			"from", rtp->rtcp->local_addr_str);
+			"from", rtp->rtcp->local_addr_str,
+			"mes", rtp->rxmes);
+
 	ast_rtp_publish_rtcp_message(instance, ast_rtp_rtcp_sent_type(),
 			rtcp_report, message_blob);
 
@@ -5118,7 +5170,8 @@ static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *fr
 		} else {
 			if (rtp->rtcp && rtp->rtcp->schedid < 0) {
 				#ifndef RTCP_PRODUCER_PLATFORM
-				ast_debug_rtcp(1, "(%p) RTCP starting transmission\n", instance);
+				ast_debug_rtcp(2, "(%s) RTCP starting transmission in %u ms\n",
+					ast_rtp_instance_get_channel_id(instance), ast_rtcp_calc_interval(rtp));
 				ao2_ref(instance, +1);
 				rtp->rtcp->schedid = ast_sched_add(rtp->sched, ast_rtcp_calc_interval(rtp), ast_rtcp_write, instance);
 				if (rtp->rtcp->schedid < 0) {
@@ -5408,8 +5461,9 @@ static int ast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *fr
 	format = frame->subclass.format;
 	if (ast_format_cmp(rtp->lasttxformat, format) == AST_FORMAT_CMP_NOT_EQUAL) {
 		/* Oh dear, if the format changed we will have to set up a new smoother */
-		ast_debug_rtp(1, "(%p) RTP ooh, format changed from %s to %s\n",
-			instance, ast_format_get_name(rtp->lasttxformat),
+		ast_debug_rtp(1, "(%s) RTP ooh, format changed from %s to %s\n",
+			ast_rtp_instance_get_channel_id(instance),
+			ast_format_get_name(rtp->lasttxformat),
 			ast_format_get_name(frame->subclass.format));
 		ao2_replace(rtp->lasttxformat, format);
 		if (rtp->smoother) {
@@ -5472,43 +5526,185 @@ static int ast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *fr
 	return 0;
 }
 
-static void calc_rxstamp(struct timeval *tv, struct ast_rtp *rtp, unsigned int timestamp, int mark)
+static void calc_rxstamp_and_jitter(struct timeval *tv,
+	struct ast_rtp *rtp, unsigned int rx_rtp_ts,
+	int mark)
 {
+	int rate = ast_rtp_get_rate(rtp->f.subclass.format);
+
+	double jitter = 0.0;
+	double prev_jitter = 0.0;
 	struct timeval now;
 	struct timeval tmp;
-	double transit;
-	double current_time;
-	double d;
-	double dtv;
-	double prog;
-	int rate = ast_rtp_get_rate(rtp->f.subclass.format);
+	double rxnow;
+	double arrival_sec;
+	unsigned int arrival;
+	int transit;
+	int d;
+
+	gettimeofday(&now,NULL);
+
+	if (rtp->rxcount == 1 || mark) {
+		rtp->rxstart = ast_tv2double(&now);
+		rtp->remote_seed_rx_rtp_ts = rx_rtp_ts;
 
-	if ((!rtp->rxcore.tv_sec && !rtp->rxcore.tv_usec) || mark) {
-		gettimeofday(&rtp->rxcore, NULL);
-		rtp->drxcore = (double) rtp->rxcore.tv_sec + (double) rtp->rxcore.tv_usec / 1000000;
-		/* map timestamp to a real time */
-		rtp->seedrxts = timestamp; /* Their RTP timestamp started with this */
-		tmp = ast_samp2tv(timestamp, rate);
+		/*
+		 * "tv" is placed in the received frame's
+		 * "delivered" field and when this frame is
+		 * sent out again on the other side, it's
+		 * used to calculate the timestamp on the
+		 * outgoing RTP packets.
+		 *
+		 * NOTE: We need to do integer math here
+		 * because double math rounding issues can
+		 * generate incorrect timestamps.
+		 */
+		rtp->rxcore = now;
+		tmp = ast_samp2tv(rx_rtp_ts, rate);
 		rtp->rxcore = ast_tvsub(rtp->rxcore, tmp);
-		/* Round to 0.1ms for nice, pretty timestamps */
 		rtp->rxcore.tv_usec -= rtp->rxcore.tv_usec % 100;
+		*tv = ast_tvadd(rtp->rxcore, tmp);
+
+		ast_debug_rtcp(3, "%s: "
+			"Seed ts: %u current time: %f\n",
+			ast_rtp_instance_get_channel_id(rtp->owner)
+			, rx_rtp_ts
+			, rtp->rxstart
+		);
+
+		return;
 	}
 
-	gettimeofday(&now,NULL);
-	/* rxcore is the mapping between the RTP timestamp and _our_ real time from gettimeofday() */
-	tmp = ast_samp2tv(timestamp, rate);
+	tmp = ast_samp2tv(rx_rtp_ts, rate);
+	/* See the comment about "tv" above. Even if
+	 * we don't use this received packet for jitter
+	 * calculations, we still need to set tv so the
+	 * timestamp will be correct when this packet is
+	 * sent out again.
+	 */
 	*tv = ast_tvadd(rtp->rxcore, tmp);
 
-	prog = (double)((timestamp-rtp->seedrxts)/(float)(rate));
-	dtv = (double)rtp->drxcore + (double)(prog);
-	current_time = (double)now.tv_sec + (double)now.tv_usec/1000000;
-	transit = current_time - dtv;
-	d = transit - rtp->rxtransit;
-	rtp->rxtransit = transit;
-	if (d<0) {
-		d=-d;
+	/*
+	 * The first few packets are generally unstable so let's
+	 * not use them in the calculations.
+	 */
+	if (rtp->rxcount < RTP_IGNORE_FIRST_PACKETS_COUNT) {
+		ast_debug_rtcp(3, "%s: Packet %d < %d.  Ignoring\n",
+			ast_rtp_instance_get_channel_id(rtp->owner)
+			, rtp->rxcount
+			, RTP_IGNORE_FIRST_PACKETS_COUNT
+		);
+
+		return;
 	}
-	rtp->rxjitter += (1./16.) * (d - rtp->rxjitter);
+
+	/*
+	 * First good packet. Capture the start time and timestamp
+	 * but don't actually use this packet for calculation.
+	 */
+	if (rtp->rxcount == RTP_IGNORE_FIRST_PACKETS_COUNT) {
+		rtp->rxstart_stable = ast_tv2double(&now);
+		rtp->remote_seed_rx_rtp_ts_stable = rx_rtp_ts;
+		rtp->last_transit_time_samples = -rx_rtp_ts;
+
+		ast_debug_rtcp(3, "%s: "
+			"pkt: %5u Stable Seed ts: %u current time: %f\n",
+			ast_rtp_instance_get_channel_id(rtp->owner)
+			, rtp->rxcount
+			, rx_rtp_ts
+			, rtp->rxstart_stable
+		);
+
+		return;
+	}
+
+	/*
+	 * If the current packet isn't in sequence, don't
+	 * use it in any calculations as remote_current_rx_rtp_ts
+	 * is not going to be correct.
+	 */
+	if (rtp->lastrxseqno != rtp->prevrxseqno + 1) {
+		ast_debug_rtcp(3, "%s: Current packet seq %d != last packet seq %d + 1.  Ignoring\n",
+			ast_rtp_instance_get_channel_id(rtp->owner)
+			, rtp->lastrxseqno
+			, rtp->prevrxseqno
+		);
+
+		return;
+	}
+
+	/*
+	 * The following calculations are taken from
+	 * https://www.rfc-editor.org/rfc/rfc3550#appendix-A.8
+	 *
+	 * The received rtp timestamp is the random "seed"
+	 * timestamp chosen by the sender when they sent the
+	 * first packet, plus the number of samples since then.
+	 *
+	 * To get our arrival time in the same units, we
+	 * calculate the time difference in seconds between
+	 * when we received the first packet and when we
+	 * received this packet and convert that to samples.
+	 */
+	rxnow = ast_tv2double(&now);
+	arrival_sec = rxnow - rtp->rxstart_stable;
+	arrival = ast_sec2samp(arrival_sec, rate);
+
+	/*
+	 * Now we can use the exact formula in
+	 * https://www.rfc-editor.org/rfc/rfc3550#appendix-A.8 :
+	 *
+	 * int transit = arrival - r->ts;
+	 * int d = transit - s->transit;
+	 * s->transit = transit;
+	 * if (d < 0) d = -d;
+	 * s->jitter += (1./16.) * ((double)d - s->jitter);
+	 *
+	 * Our rx_rtp_ts is their r->ts.
+	 * Our rtp->last_transit_time_samples is their s->transit.
+	 * Our rtp->rxjitter is their s->jitter.
+	 */
+	transit = arrival - rx_rtp_ts;
+	d = transit - rtp->last_transit_time_samples;
+
+	if (d < 0) {
+		d = -d;
+	}
+
+	prev_jitter = rtp->rxjitter_samples;
+	jitter = (1.0/16.0) * (((double)d) - prev_jitter);
+	rtp->rxjitter_samples = prev_jitter + jitter;
+
+	/*
+	 * We need to hang on to jitter in both samples and seconds.
+	 */
+	rtp->rxjitter = ast_samp2sec(rtp->rxjitter_samples, rate);
+
+	ast_debug_rtcp(3, "%s: pkt: %5u "
+		"Arrival sec: %7.3f  Arrival ts: %10u  RX ts: %10u "
+		"Transit samp: %6d Last transit samp: %6d d: %4d "
+		"Curr jitter: %7.0f(%7.3f) Prev Jitter: %7.0f(%7.3f) New Jitter: %7.0f(%7.3f)\n",
+		ast_rtp_instance_get_channel_id(rtp->owner)
+		, rtp->rxcount
+		, arrival_sec
+		, arrival
+		, rx_rtp_ts
+		, transit
+		, rtp->last_transit_time_samples
+		, d
+		, jitter
+		, ast_samp2sec(jitter, rate)
+		, prev_jitter
+		, ast_samp2sec(prev_jitter, rate)
+		, rtp->rxjitter_samples
+		, rtp->rxjitter
+		);
+
+	rtp->last_transit_time_samples = transit;
+
+	/*
+	 * Update all the stats.
+	 */
 	if (rtp->rtcp) {
 		if (rtp->rxjitter > rtp->rtcp->maxrxjitter)
 			rtp->rtcp->maxrxjitter = rtp->rxjitter;
@@ -5517,9 +5713,12 @@ static void calc_rxstamp(struct timeval *tv, struct ast_rtp *rtp, unsigned int t
 		if (rtp->rtcp && rtp->rxjitter < rtp->rtcp->minrxjitter)
 			rtp->rtcp->minrxjitter = rtp->rxjitter;
 
-		calc_mean_and_standard_deviation(rtp->rxjitter, &rtp->rtcp->normdev_rxjitter,
-			&rtp->rtcp->stdev_rxjitter, &rtp->rtcp->rxjitter_count);
+		calc_mean_and_standard_deviation(rtp->rxjitter,
+			&rtp->rtcp->normdev_rxjitter, &rtp->rtcp->stdev_rxjitter,
+			&rtp->rtcp->rxjitter_count);
 	}
+
+	return;
 }
 
 static struct ast_frame *create_dtmf_frame(struct ast_rtp_instance *instance, enum ast_frame_type type, int compensate)
@@ -5887,22 +6086,23 @@ static int update_rtt_stats(struct ast_rtp *rtp, unsigned int lsr, unsigned int
  */
 static void update_jitter_stats(struct ast_rtp *rtp, unsigned int ia_jitter)
 {
-	double reported_jitter;
+	int rate = ast_rtp_get_rate(rtp->f.subclass.format);
+
+	rtp->rtcp->reported_jitter = ast_samp2sec(ia_jitter, rate);
 
-	rtp->rtcp->reported_jitter = ia_jitter;
-	reported_jitter = (double) rtp->rtcp->reported_jitter;
 	if (rtp->rtcp->reported_jitter_count == 0) {
-		rtp->rtcp->reported_minjitter = reported_jitter;
+		rtp->rtcp->reported_minjitter = rtp->rtcp->reported_jitter;
 	}
-	if (reported_jitter < rtp->rtcp->reported_minjitter) {
-		rtp->rtcp->reported_minjitter = reported_jitter;
+	if (rtp->rtcp->reported_jitter < rtp->rtcp->reported_minjitter) {
+		rtp->rtcp->reported_minjitter = rtp->rtcp->reported_jitter;
 	}
-	if (reported_jitter > rtp->rtcp->reported_maxjitter) {
-		rtp->rtcp->reported_maxjitter = reported_jitter;
+	if (rtp->rtcp->reported_jitter > rtp->rtcp->reported_maxjitter) {
+		rtp->rtcp->reported_maxjitter = rtp->rtcp->reported_jitter;
 	}
 
-	calc_mean_and_standard_deviation(reported_jitter, &rtp->rtcp->reported_normdev_jitter,
-		&rtp->rtcp->reported_stdev_jitter, &rtp->rtcp->reported_jitter_count);
+	calc_mean_and_standard_deviation(rtp->rtcp->reported_jitter,
+		&rtp->rtcp->reported_normdev_jitter, &rtp->rtcp->reported_stdev_jitter,
+		&rtp->rtcp->reported_jitter_count);
 }
 
 /*!
@@ -5929,6 +6129,159 @@ static void update_lost_stats(struct ast_rtp *rtp, unsigned int lost_packets)
 		&rtp->rtcp->reported_stdev_lost, &rtp->rtcp->reported_lost_count);
 }
 
+#define RESCALE(in, inmin, inmax, outmin, outmax) ((((in - inmin)/(inmax-inmin))*(outmax-outmin))+outmin)
+/*!
+ * \brief Calculate a "media experience score" based on given data
+ *
+ * Technically, a mean opinion score (MOS) cannot be calculated without the involvement
+ * of human eyes (video) and ears (audio). Thus instead we'll approximate an opinion
+ * using the given parameters, and call it a media experience score.
+ *
+ * The tallied score is based upon recommendations and formulas from ITU-T G.107,
+ * ITU-T G.109, ITU-T G.113, and other various internet sources.
+ *
+ * \param instance RTP instance
+ * \param normdevrtt The average round trip time
+ * \param normdev_rxjitter The smoothed jitter
+ * \param stdev_rxjitter The jitter standard deviation value
+ * \param normdev_rxlost The average number of packets lost since last check
+ *
+ * \return A media experience score.
+ *
+ * \note The calculations in this function could probably be simplified
+ * but calculating a MOS using the information available publicly,
+ * then re-scaling it to 0.0 -> 100.0 makes the process clearer and
+ * easier to troubleshoot or change.
+ */
+static double calc_media_experience_score(struct ast_rtp_instance *instance,
+	double normdevrtt, double normdev_rxjitter, double stdev_rxjitter,
+	double normdev_rxlost)
+{
+	double r_value;
+	double pseudo_mos;
+	double mes = 0;
+
+	/*
+	 * While the media itself might be okay, a significant enough delay could make
+	 * for an unpleasant user experience.
+	 *
+	 * Calculate the effective latency by using the given round trip time, and adding
+	 * jitter scaled according to its standard deviation. The scaling is done in order
+	 * to increase jitter's weight since a higher deviation can result in poorer overall
+	 * quality.
+	 */
+	double effective_latency = (normdevrtt * 1000)
+		+ ((normdev_rxjitter * 2) * (stdev_rxjitter / 3))
+		+ 10;
+
+	/*
+	 * Using the defaults for the standard transmission rating factor ("R" value)
+	 * one arrives at 93.2 (see ITU-T G.107 for more details), so we'll use that
+	 * as the starting value and subtract deficiencies that could affect quality.
+	 *
+	 * Calculate the impact of the effective latency. Influence increases with
+	 * values over 160 as the significant "lag" can degrade user experience.
+	 */
+	if (effective_latency < 160) {
+		r_value = 93.2 - (effective_latency / 40);
+	} else {
+		r_value = 93.2 - (effective_latency - 120) / 10;
+	}
+
+	/* Next evaluate the impact of lost packets */
+	r_value = r_value - (normdev_rxlost * 2.0);
+
+	/*
+	 * Finally convert the "R" value into a opinion/quality score between 1 (really anything
+	 * below 3 should be considered poor) and 4.5 (the highest achievable for VOIP).
+	 */
+	if (r_value < 0) {
+		pseudo_mos = 1.0;
+	} else if (r_value > 100) {
+		pseudo_mos = 4.5;
+	} else {
+		pseudo_mos = 1 + (0.035 * r_value) + (r_value * (r_value - 60) * (100 - r_value) * 0.0000007);
+	}
+
+	/*
+	 * We're going to rescale the 0.0->5.0 pseudo_mos to the 0.0->100.0 MES.
+	 * For those ranges, we could actually just multiply the pseudo_mos
+	 * by 20 but we may want to change the scale later.
+	 */
+	mes = RESCALE(pseudo_mos, 0.0, 5.0, 0.0, 100.0);
+
+	return mes;
+}
+
+/*!
+ * \internal
+ * \brief Update MES stats based on info received in an SR or RR.
+ * This is RTP we sent and they received.
+ */
+static void update_reported_mes_stats(struct ast_rtp *rtp)
+{
+	double mes = calc_media_experience_score(rtp->owner,
+		rtp->rtcp->normdevrtt,
+		rtp->rtcp->reported_jitter,
+		rtp->rtcp->reported_stdev_jitter,
+		rtp->rtcp->reported_normdev_lost);
+
+	rtp->rtcp->reported_mes = mes;
+	if (rtp->rtcp->reported_mes_count == 0) {
+		rtp->rtcp->reported_minmes = mes;
+	}
+	if (mes < rtp->rtcp->reported_minmes) {
+		rtp->rtcp->reported_minmes = mes;
+	}
+	if (mes > rtp->rtcp->reported_maxmes) {
+		rtp->rtcp->reported_maxmes = mes;
+	}
+
+	calc_mean_and_standard_deviation(mes, &rtp->rtcp->reported_normdev_mes,
+		&rtp->rtcp->reported_stdev_mes, &rtp->rtcp->reported_mes_count);
+
+	ast_debug_rtcp(2, "%s: rtt: %.9f j: %.9f sjh: %.9f lost: %.9f mes: %4.1f\n",
+		ast_rtp_instance_get_channel_id(rtp->owner),
+		rtp->rtcp->normdevrtt,
+				rtp->rtcp->reported_jitter,
+				rtp->rtcp->reported_stdev_jitter,
+				rtp->rtcp->reported_normdev_lost, mes);
+}
+
+/*!
+ * \internal
+ * \brief Update MES stats based on info we will send in an SR or RR.
+ * This is RTP they sent and we received.
+ */
+static void update_local_mes_stats(struct ast_rtp *rtp)
+{
+	rtp->rxmes = calc_media_experience_score(rtp->owner,
+		rtp->rtcp->normdevrtt,
+		rtp->rxjitter,
+		rtp->rtcp->stdev_rxjitter,
+		rtp->rtcp->normdev_rxlost);
+
+	if (rtp->rtcp->rxmes_count == 0) {
+		rtp->rtcp->minrxmes = rtp->rxmes;
+	}
+	if (rtp->rxmes < rtp->rtcp->minrxmes) {
+		rtp->rtcp->minrxmes = rtp->rxmes;
+	}
+	if (rtp->rxmes > rtp->rtcp->maxrxmes) {
+		rtp->rtcp->maxrxmes = rtp->rxmes;
+	}
+
+	calc_mean_and_standard_deviation(rtp->rxmes, &rtp->rtcp->normdev_rxmes,
+		&rtp->rtcp->stdev_rxmes, &rtp->rtcp->rxmes_count);
+
+	ast_debug_rtcp(2, "   %s: rtt: %.9f j: %.9f sjh: %.9f lost: %.9f mes: %4.1f\n",
+		ast_rtp_instance_get_channel_id(rtp->owner),
+		rtp->rtcp->normdevrtt,
+				rtp->rxjitter,
+				rtp->rtcp->stdev_rxjitter,
+				rtp->rtcp->normdev_rxlost, rtp->rxmes);
+}
+
 /*! \pre instance is locked */
 static struct ast_rtp_instance *__rtp_find_instance_by_ssrc(struct ast_rtp_instance *instance,
 	struct ast_rtp *rtp, unsigned int ssrc, int source)
@@ -6187,23 +6540,26 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, s
 
 	packetwords = len / 4;
 
-	ast_debug_rtcp(1, "(%p) RTCP got report of %d bytes from %s\n",
-		instance, len, ast_sockaddr_stringify(addr));
+	ast_debug_rtcp(2, "(%s) RTCP got report of %d bytes from %s\n",
+		ast_rtp_instance_get_channel_id(instance),
+		len, ast_sockaddr_stringify(addr));
 
 	/*
 	 * Validate the RTCP packet according to an adapted and slightly
 	 * modified RFC3550 validation algorithm.
 	 */
 	if (packetwords < RTCP_HEADER_SSRC_LENGTH) {
-		ast_debug_rtcp(1, "(%p) RTCP %p -- from %s: Frame size (%u words) is too short\n",
-			instance, transport_rtp, ast_sockaddr_stringify(addr), packetwords);
+		ast_debug_rtcp(2, "(%s) RTCP %p -- from %s: Frame size (%u words) is too short\n",
+			ast_rtp_instance_get_channel_id(instance),
+			transport_rtp, ast_sockaddr_stringify(addr), packetwords);
 		return &ast_null_frame;
 	}
 	position = 0;
 	first_word = ntohl(rtcpheader[position]);
 	if ((first_word & RTCP_VALID_MASK) != RTCP_VALID_VALUE) {
-		ast_debug_rtcp(1, "(%p) RTCP %p -- from %s: Failed first packet validity check\n",
-			instance, transport_rtp, ast_sockaddr_stringify(addr));
+		ast_debug_rtcp(2, "(%s) RTCP %p -- from %s: Failed first packet validity check\n",
+			ast_rtp_instance_get_channel_id(instance),
+			transport_rtp, ast_sockaddr_stringify(addr));
 		return &ast_null_frame;
 	}
 	do {
@@ -6214,8 +6570,9 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, s
 		first_word = ntohl(rtcpheader[position]);
 	} while ((first_word & RTCP_VERSION_MASK_SHIFTED) == RTCP_VERSION_SHIFTED);
 	if (position != packetwords) {
-		ast_debug_rtcp(1, "(%p) RTCP %p -- from %s: Failed packet version or length check\n",
-			instance, transport_rtp, ast_sockaddr_stringify(addr));
+		ast_debug_rtcp(2, "(%s) RTCP %p -- from %s: Failed packet version or length check\n",
+			ast_rtp_instance_get_channel_id(instance),
+			transport_rtp, ast_sockaddr_stringify(addr));
 		return &ast_null_frame;
 	}
 
@@ -6484,43 +6841,55 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, s
 				report_block->ia_jitter =  ntohl(rtcpheader[i + 3]);
 				report_block->lsr = ntohl(rtcpheader[i + 4]);
 				report_block->dlsr = ntohl(rtcpheader[i + 5]);
-				if (report_block->lsr
-					&& update_rtt_stats(rtp, report_block->lsr, report_block->dlsr)
-					&& rtcp_debug_test_addr(addr)) {
-					struct timeval now;
-					unsigned int lsr_now, lsw, msw;
-					gettimeofday(&now, NULL);
-					timeval2ntp(now, &msw, &lsw);
-					lsr_now = (((msw & 0xffff) << 16) | ((lsw & 0xffff0000) >> 16));
-					ast_verbose("Internal RTCP NTP clock skew detected: "
-							   "lsr=%u, now=%u, dlsr=%u (%u:%03ums), "
+				if (report_block->lsr) {
+					int skewed = update_rtt_stats(rtp, report_block->lsr, report_block->dlsr);
+					if (skewed && rtcp_debug_test_addr(addr)) {
+						struct timeval now;
+						unsigned int lsr_now, lsw, msw;
+						gettimeofday(&now, NULL);
+						timeval2ntp(now, &msw, &lsw);
+						lsr_now = (((msw & 0xffff) << 16) | ((lsw & 0xffff0000) >> 16));
+						ast_verbose("Internal RTCP NTP clock skew detected: "
+							"lsr=%u, now=%u, dlsr=%u (%u:%03ums), "
 							"diff=%u\n",
 							report_block->lsr, lsr_now, report_block->dlsr, report_block->dlsr / 65536,
 							(report_block->dlsr % 65536) * 1000 / 65536,
 							report_block->dlsr - (lsr_now - report_block->lsr));
+					}
 				}
 				update_jitter_stats(rtp, report_block->ia_jitter);
 				update_lost_stats(rtp, report_block->lost_count.packets);
+				/*
+				 * update_reported_mes_stats must be called AFTER
+				 * update_rtt_stats, update_jitter_stats and
+				 * update_lost_stats.
+				 */
+				update_reported_mes_stats(rtp);
 
 				if (rtcp_debug_test_addr(addr)) {
+					int rate = ast_rtp_get_rate(rtp->f.subclass.format);
+
 					ast_verbose("  Fraction lost: %d\n", report_block->lost_count.fraction);
 					ast_verbose("  Packets lost so far: %u\n", report_block->lost_count.packets);
 					ast_verbose("  Highest sequence number: %u\n", report_block->highest_seq_no & 0x0000ffff);
 					ast_verbose("  Sequence number cycles: %u\n", report_block->highest_seq_no >> 16);
-					ast_verbose("  Interarrival jitter: %u\n", report_block->ia_jitter);
+					ast_verbose("  Interarrival jitter (samp): %u\n", report_block->ia_jitter);
+					ast_verbose("  Interarrival jitter (secs): %.6f\n", ast_samp2sec(report_block->ia_jitter, rate));
 					ast_verbose("  Last SR(our NTP): %lu.%010lu\n",(unsigned long)(report_block->lsr) >> 16,((unsigned long)(report_block->lsr) << 16) * 4096);
 					ast_verbose("  DLSR: %4.4f (sec)\n",(double)report_block->dlsr / 65536.0);
 					ast_verbose("  RTT: %4.4f(sec)\n", rtp->rtcp->rtt);
+					ast_verbose("  MES: %4.1f\n", rtp->rtcp->reported_mes);
 				}
 			}
 			/* If and when we handle more than one report block, this should occur outside
 			 * this loop.
 			 */
 
-			message_blob = ast_json_pack("{s: s, s: s, s: f}",
+			message_blob = ast_json_pack("{s: s, s: s, s: f, s: f}",
 				"from", ast_sockaddr_stringify(addr),
 				"to", transport_rtp->rtcp->local_addr_str,
-				"rtt", rtp->rtcp->rtt);
+				"rtt", rtp->rtcp->rtt,
+				"mes", rtp->rtcp->reported_mes);
 			ast_rtp_publish_rtcp_message(instance, ast_rtp_rtcp_received_type(),
 					rtcp_report,
 					message_blob);
@@ -7411,7 +7780,8 @@ static struct ast_frame *ast_rtp_interpret(struct ast_rtp_instance *instance, st
 		struct ast_frame *f;
 
 		/* Update statistics for jitter so they are correct in RTCP */
-		calc_rxstamp(&rxtime, rtp, timestamp, mark);
+		calc_rxstamp_and_jitter(&rxtime, rtp, timestamp, mark);
+
 
 		/* When doing P2P we don't need to raise any frames about SSRC change to the core */
 		while ((f = AST_LIST_REMOVE_HEAD(&frames, frame_list)) != NULL) {
@@ -7562,7 +7932,7 @@ static struct ast_frame *ast_rtp_interpret(struct ast_rtp_instance *instance, st
 		if (ast_format_cache_is_slinear(rtp->f.subclass.format)) {
 			ast_frame_byteswap_be(&rtp->f);
 		}
-		calc_rxstamp(&rtp->f.delivery, rtp, timestamp, mark);
+		calc_rxstamp_and_jitter(&rtp->f.delivery, rtp, timestamp, mark);
 		/* Add timing data to let ast_generic_bridge() put the frame into a jitterbuf */
 		ast_set_flag(&rtp->f, AST_FRFLAG_HAS_TIMING_INFO);
 		rtp->f.ts = timestamp / (ast_rtp_get_rate(rtp->f.subclass.format) / 1000);
@@ -7571,7 +7941,7 @@ static struct ast_frame *ast_rtp_interpret(struct ast_rtp_instance *instance, st
 		/* Video -- samples is # of samples vs. 90000 */
 		if (!rtp->lastividtimestamp)
 			rtp->lastividtimestamp = timestamp;
-		calc_rxstamp(&rtp->f.delivery, rtp, timestamp, mark);
+		calc_rxstamp_and_jitter(&rtp->f.delivery, rtp, timestamp, mark);
 		ast_set_flag(&rtp->f, AST_FRFLAG_HAS_TIMING_INFO);
 		rtp->f.ts = timestamp / (ast_rtp_get_rate(rtp->f.subclass.format) / 1000);
 		rtp->f.samples = timestamp - rtp->lastividtimestamp;
@@ -8020,6 +8390,8 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
 	bundled = (child || AST_VECTOR_SIZE(&rtp->ssrc_mapping)) ? 1 : 0;
 
 	prev_seqno = rtp->lastrxseqno;
+	/* We need to save lastrxseqno for use by jitter before resetting it. */
+	rtp->prevrxseqno = rtp->lastrxseqno;
 	rtp->lastrxseqno = seqno;
 
 	if (!rtp->recv_buffer) {
@@ -8483,7 +8855,8 @@ static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_pro
 #endif
 			}
 
-			ast_debug_rtcp(1, "(%p) RTCP setup on RTP instance\n", instance);
+			ast_debug_rtcp(1, "(%s) RTCP setup on RTP instance\n",
+				ast_rtp_instance_get_channel_id(instance));
 		} else {
 			if (rtp->rtcp) {
 				if (rtp->rtcp->schedid > -1) {
@@ -8527,6 +8900,8 @@ static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_pro
 				ast_free(rtp->rtcp->local_addr_str);
 				ast_free(rtp->rtcp);
 				rtp->rtcp = NULL;
+				ast_debug_rtcp(1, "(%s) RTCP torn down on RTP instance\n",
+					ast_rtp_instance_get_channel_id(instance));
 			}
 		}
 	} else if (property == AST_RTP_PROPERTY_ASYMMETRIC_CODEC) {
@@ -8767,7 +9142,7 @@ static int ast_rtp_get_stat(struct ast_rtp_instance *instance, struct ast_rtp_in
 	AST_RTP_STAT_TERMINATOR(AST_RTP_INSTANCE_STAT_COMBINED_LOSS);
 
 	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_TXJITTER, AST_RTP_INSTANCE_STAT_COMBINED_JITTER, stats->txjitter, rtp->rxjitter);
-	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_RXJITTER, AST_RTP_INSTANCE_STAT_COMBINED_JITTER, stats->rxjitter, rtp->rtcp->reported_jitter / (unsigned int) 65536.0);
+	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_RXJITTER, AST_RTP_INSTANCE_STAT_COMBINED_JITTER, stats->rxjitter, rtp->rtcp->reported_jitter);
 	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_MAXJITTER, AST_RTP_INSTANCE_STAT_COMBINED_JITTER, stats->remote_maxjitter, rtp->rtcp->reported_maxjitter);
 	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_MINJITTER, AST_RTP_INSTANCE_STAT_COMBINED_JITTER, stats->remote_minjitter, rtp->rtcp->reported_minjitter);
 	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_NORMDEVJITTER, AST_RTP_INSTANCE_STAT_COMBINED_JITTER, stats->remote_normdevjitter, rtp->rtcp->reported_normdev_jitter);
@@ -8785,6 +9160,19 @@ static int ast_rtp_get_stat(struct ast_rtp_instance *instance, struct ast_rtp_in
 	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_STDEVRTT, AST_RTP_INSTANCE_STAT_COMBINED_RTT, stats->stdevrtt, rtp->rtcp->stdevrtt);
 	AST_RTP_STAT_TERMINATOR(AST_RTP_INSTANCE_STAT_COMBINED_RTT);
 
+	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_TXMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->txmes, rtp->rxmes);
+	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_RXMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->rxmes, rtp->rtcp->reported_mes);
+	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_MAXMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->remote_maxmes, rtp->rtcp->reported_maxmes);
+	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_MINMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->remote_minmes, rtp->rtcp->reported_minmes);
+	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_NORMDEVMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->remote_normdevmes, rtp->rtcp->reported_normdev_mes);
+	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_STDEVMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->remote_stdevmes, rtp->rtcp->reported_stdev_mes);
+	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_LOCAL_MAXMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->local_maxmes, rtp->rtcp->maxrxmes);
+	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_LOCAL_MINMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->local_minmes, rtp->rtcp->minrxmes);
+	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_LOCAL_NORMDEVMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->local_normdevmes, rtp->rtcp->normdev_rxmes);
+	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_LOCAL_STDEVMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->local_stdevmes, rtp->rtcp->stdev_rxjitter);
+	AST_RTP_STAT_TERMINATOR(AST_RTP_INSTANCE_STAT_COMBINED_MES);
+
+
 	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_LOCAL_SSRC, -1, stats->local_ssrc, rtp->ssrc);
 	AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_SSRC, -1, stats->remote_ssrc, rtp->themssrc);
 	AST_RTP_STAT_STRCPY(AST_RTP_INSTANCE_STAT_CHANNEL_UNIQUEID, -1, stats->channel_uniqueid, ast_rtp_instance_get_channel_id(instance));
@@ -8840,6 +9228,8 @@ static void ast_rtp_stop(struct ast_rtp_instance *instance)
 	}
 	ao2_lock(instance);
 #endif
+	ast_debug_rtp(1, "(%s) RTP Stop\n",
+		ast_rtp_instance_get_channel_id(instance));
 
 	if (rtp->rtcp && rtp->rtcp->schedid > -1) {
 		ao2_unlock(instance);
diff --git a/res/res_speech.c b/res/res_speech.c
index d425fde181abbf802a1dbfa4dd0a9fa8a48a9443..2438e7953cf44f4c75b01b2b1dea924c425a2176 100644
--- a/res/res_speech.c
+++ b/res/res_speech.c
@@ -42,7 +42,7 @@ static AST_RWLIST_HEAD_STATIC(engines, ast_speech_engine);
 static struct ast_speech_engine *default_engine = NULL;
 
 /*! \brief Find a speech recognition engine of specified name, if NULL then use the default one */
-static struct ast_speech_engine *find_engine(const char *engine_name)
+struct ast_speech_engine *ast_speech_find_engine(const char *engine_name)
 {
 	struct ast_speech_engine *engine = NULL;
 
@@ -185,7 +185,7 @@ struct ast_speech *ast_speech_new(const char *engine_name, const struct ast_form
 	RAII_VAR(struct ast_format *, best, NULL, ao2_cleanup);
 
 	/* Try to find the speech recognition engine that was requested */
-	if (!(engine = find_engine(engine_name)))
+	if (!(engine = ast_speech_find_engine(engine_name)))
 		return NULL;
 
 	joint = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
@@ -313,7 +313,7 @@ int ast_speech_register(struct ast_speech_engine *engine)
 	}
 
 	/* If an engine is already loaded with this name, error out */
-	if (find_engine(engine->name)) {
+	if (ast_speech_find_engine(engine->name)) {
 		ast_log(LOG_WARNING, "Speech recognition engine '%s' already exists.\n", engine->name);
 		return -1;
 	}
@@ -366,6 +366,36 @@ struct ast_speech_engine *ast_speech_unregister2(const char *engine_name)
 	return engine;
 }
 
+void ast_speech_unregister_engines(
+	int (*should_unregister)(const struct ast_speech_engine *engine, void *data), void *data,
+	void (*on_unregistered)(void *obj))
+{
+	struct ast_speech_engine *engine = NULL;
+
+	if (!should_unregister) {
+		return;
+	}
+
+	AST_RWLIST_WRLOCK(&engines);
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&engines, engine, list) {
+		if (should_unregister(engine, data)) {
+			/* We have our engine... removed it */
+			AST_RWLIST_REMOVE_CURRENT(list);
+			/* If this was the default engine, we need to pick a new one */
+			if (engine == default_engine) {
+				default_engine = AST_RWLIST_FIRST(&engines);
+			}
+			ast_verb(2, "Unregistered speech recognition engine '%s'\n", engine->name);
+			/* All went well */
+			if (on_unregistered) {
+				on_unregistered(engine);
+			}
+		}
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+	AST_RWLIST_UNLOCK(&engines);
+}
+
 static int unload_module(void)
 {
 	/* We can not be unloaded */
diff --git a/res/res_speech_aeap.c b/res/res_speech_aeap.c
new file mode 100644
index 0000000000000000000000000000000000000000..1aa8041d53a189bb28e2c54426f8f14538cfa306
--- /dev/null
+++ b/res/res_speech_aeap.c
@@ -0,0 +1,740 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Asterisk External Application Speech Engine
+ *
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/astobj2.h"
+#include "asterisk/config.h"
+#include "asterisk/format.h"
+#include "asterisk/format_cap.h"
+#include "asterisk/json.h"
+#include "asterisk/module.h"
+#include "asterisk/speech.h"
+#include "asterisk/sorcery.h"
+
+#include "asterisk/res_aeap.h"
+#include "asterisk/res_aeap_message.h"
+
+#define SPEECH_AEAP_VERSION "0.1.0"
+#define SPEECH_PROTOCOL "speech_to_text"
+
+#define CONNECTION_TIMEOUT 2000
+
+#define log_error(obj, fmt, ...) \
+	ast_log(LOG_ERROR, "AEAP speech (%p): " fmt "\n", obj, ##__VA_ARGS__)
+
+static struct ast_json *custom_fields_to_params(const struct ast_variable *variables)
+{
+	const struct ast_variable *i;
+	struct ast_json *obj;
+
+	if (!variables) {
+		return NULL;
+	}
+
+	obj = ast_json_object_create();
+	if (!obj) {
+		return NULL;
+	}
+
+	for (i = variables; i; i = i->next) {
+		if (i->name[0] == '@' && i->name[1]) {
+			ast_json_object_set(obj, i->name + 1, ast_json_string_create(i->value));
+		}
+	}
+
+	return obj;
+}
+
+/*!
+ * \internal
+ * \brief Create, and send a request to the external application
+ *
+ * Create, then sends a request to an Asterisk external application, and then blocks
+ * until a response is received or a time out occurs. Since this method waits until
+ * receiving a response the returned result is guaranteed to be pass/fail based upon
+ * a response handler's result.
+ *
+ * \param aeap Pointer to an Asterisk external application object
+ * \param name The name of the request to send
+ * \param json The core json request data
+ * \param data Optional user data to associate with request/response
+ *
+ * \returns 0 on success, -1 on error
+ */
+static int speech_aeap_send_request(struct ast_aeap *aeap, const char *name,
+	struct ast_json *json, void *data)
+{
+	/*
+	 * Wait for a response. Also since we're blocking,
+	 * data is expected to be on the stack so no cleanup required.
+	 */
+	struct ast_aeap_tsx_params tsx_params = {
+		.timeout = 1000,
+		.wait = 1,
+		.obj = data,
+	};
+
+	/* "steals" the json ref */
+	tsx_params.msg = ast_aeap_message_create_request(
+		ast_aeap_message_type_json, name, NULL, json);
+	if (!tsx_params.msg) {
+		return -1;
+	}
+
+	/* Send "steals" the json msg ref */
+	return ast_aeap_send_msg_tsx(aeap, &tsx_params);
+}
+
+/*!
+ * \internal
+ * \brief Create, and send a "get" request to an external application
+ *
+ * Basic structure of the JSON message to send:
+ *
+ \verbatim
+ { param: [<param>, ...] }
+ \endverbatim
+ *
+ * \param speech The speech engine
+ * \param param The name of the parameter to retrieve
+ * \param data User data passed to the response handler
+ *
+ * \returns 0 on success, -1 on error
+ */
+static int speech_aeap_get(struct ast_speech *speech, const char *param, void *data)
+{
+	if (!param) {
+		return -1;
+	}
+
+	/* send_request handles json ref */
+	return speech_aeap_send_request(speech->data,
+		"get", ast_json_pack("{s:[s]}", "params", param), data);
+}
+
+struct speech_param {
+	const char *name;
+	const char *value;
+};
+
+/*!
+ * \internal
+ * \brief Create, and send a "set" request to an external application
+ *
+ * Basic structure of the JSON message to send:
+ *
+ \verbatim
+ { params: { <name> : <value> }  }
+ \endverbatim
+ *
+ * \param speech The speech engine
+ * \param name The name of the parameter to set
+ * \param value The value of the parameter to set
+ *
+ * \returns 0 on success, -1 on error
+ */
+static int speech_aeap_set(struct ast_speech *speech, const char *name, const char *value)
+{
+	if (!name) {
+		return -1;
+	}
+
+	/* send_request handles json ref */
+	return speech_aeap_send_request(speech->data,
+		"set", ast_json_pack("{s:{s:s}}", "params", name, value), NULL);
+}
+
+static int handle_response_set(struct ast_aeap *aeap, struct ast_aeap_message *message, void *data)
+{
+	return 0;
+}
+
+struct speech_setting {
+	const char *param;
+	size_t len;
+	char *buf;
+};
+
+static int handle_setting(struct ast_aeap *aeap, struct ast_json_iter *iter,
+	struct speech_setting *setting)
+{
+	const char *value;
+
+	if (strcmp(ast_json_object_iter_key(iter), setting->param)) {
+		log_error(aeap, "Unable to 'get' speech setting for '%s'", setting->param);
+		return -1;
+	}
+
+	value = ast_json_string_get(ast_json_object_iter_value(iter));
+	if (!value) {
+		log_error(aeap, "No value for speech setting '%s'", setting->param);
+		return -1;
+	}
+
+	ast_copy_string(setting->buf, value, setting->len);
+	return 0;
+}
+
+static int handle_results(struct ast_aeap *aeap, struct ast_json_iter *iter,
+	struct ast_speech_result **speech_results)
+{
+	struct ast_speech_result *result = NULL;
+	struct ast_json *json_results;
+	struct ast_json *json_result;
+	size_t i;
+
+	json_results = ast_json_object_iter_value(iter);
+	if (!json_results || !speech_results) {
+		log_error(aeap, "Unable to 'get' speech results");
+		return -1;
+	}
+
+	for (i = 0; i < ast_json_array_size(json_results); ++i) {
+		if (!(result = ast_calloc(1, sizeof(*result)))) {
+			continue;
+		}
+
+		json_result = ast_json_array_get(json_results, i);
+
+		result->text = ast_strdup(ast_json_object_string_get(json_result, "text"));
+		result->score = ast_json_object_integer_get(json_result, "score");
+		result->grammar = ast_strdup(ast_json_object_string_get(json_result, "grammar"));
+		result->nbest_num = ast_json_object_integer_get(json_result, "best");
+		if (*speech_results) {
+			AST_LIST_NEXT(result, list) = *speech_results;
+			*speech_results = result;
+		} else {
+			*speech_results = result;
+		}
+	}
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Handle a "get" response from an external application
+ *
+ * Basic structure of the expected JSON message to received:
+ *
+ \verbatim
+ {
+   response: "get"
+   "params" : { <name>: <value> | [ <results> ] }
+ }
+ \endverbatim
+ *
+ * \param aeap Pointer to an Asterisk external application object
+ * \param message The received message
+ * \param data User data passed to the response handler
+ *
+ * \returns 0 on success, -1 on error
+ */
+static int handle_response_get(struct ast_aeap *aeap, struct ast_aeap_message *message, void *data)
+{
+	struct ast_json_iter *iter;
+
+	iter = ast_json_object_iter(ast_json_object_get(ast_aeap_message_data(message), "params"));
+	if (!iter) {
+		log_error(aeap, "no 'get' parameters returned");
+		return -1;
+	}
+
+	if (!strcmp(ast_json_object_iter_key(iter), "results")) {
+		return handle_results(aeap, iter, data);
+	}
+
+	return handle_setting(aeap, iter, data);
+}
+
+static int handle_response_setup(struct ast_aeap *aeap, struct ast_aeap_message *message, void *data)
+{
+	struct ast_format *format = data;
+	struct ast_json *json = ast_aeap_message_data(message);
+	const char *codec_name;
+
+	if (!json) {
+		log_error(aeap, "no 'setup' object returned");
+		return -1;
+	}
+
+	json = ast_json_object_get(json, "codecs");
+	if (!json || ast_json_array_size(json) == 0) {
+		log_error(aeap, "no 'setup' codecs available");
+		return -1;
+	}
+
+	codec_name = ast_json_object_string_get(ast_json_array_get(json, 0), "name");
+	if (!codec_name || strcmp(codec_name, ast_format_get_codec_name(format))) {
+		log_error(aeap, "setup  codec '%s' unsupported", ast_format_get_codec_name(format));
+		return -1;
+	}
+
+	return 0;
+}
+
+static const struct ast_aeap_message_handler response_handlers[] = {
+	{ "setup", handle_response_setup },
+	{ "get", handle_response_get },
+	{ "set", handle_response_set },
+};
+
+static int handle_request_set(struct ast_aeap *aeap, struct ast_aeap_message *message, void *data)
+{
+	struct ast_json_iter *iter;
+	const char *error_msg = NULL;
+
+	iter = ast_json_object_iter(ast_json_object_get(ast_aeap_message_data(message), "params"));
+	if (!iter) {
+		error_msg = "no parameter(s) requested";
+	} else if (!strcmp(ast_json_object_iter_key(iter), "results")) {
+		struct ast_speech *speech = ast_aeap_user_data_object_by_id(aeap, "speech");
+
+		if (!speech) {
+			error_msg = "no associated speech object";
+		} else if (handle_results(aeap, iter, &speech->results)) {
+			error_msg = "unable to handle results";
+		} else {
+			ast_speech_change_state(speech, AST_SPEECH_STATE_DONE);
+		}
+	} else {
+		error_msg = "can only set 'results'";
+	}
+
+	if (error_msg) {
+		log_error(aeap, "set - %s", error_msg);
+		message = ast_aeap_message_create_error(ast_aeap_message_type_json,
+			ast_aeap_message_name(message), ast_aeap_message_id(message), error_msg);
+	} else {
+		message = ast_aeap_message_create_response(ast_aeap_message_type_json,
+			ast_aeap_message_name(message), ast_aeap_message_id(message), NULL);
+	}
+
+	ast_aeap_send_msg(aeap, message);
+
+	return 0;
+}
+
+static const struct ast_aeap_message_handler request_handlers[] = {
+	{ "set", handle_request_set },
+};
+
+static struct ast_aeap_params speech_aeap_params = {
+	.response_handlers = response_handlers,
+	.response_handlers_size = ARRAY_LEN(response_handlers),
+	.request_handlers = request_handlers,
+	.request_handlers_size = ARRAY_LEN(request_handlers),
+};
+
+/*!
+ * \internal
+ * \brief Create, and connect to an external application and send initial setup
+ *
+ * Basic structure of the JSON message to send:
+ *
+ \verbatim
+ {
+   "request": "setup"
+   "codecs": [
+       {
+           "name": <name>,
+           "attributes": { <name>: <value>, ..., }
+       },
+       ...,
+   ],
+   "params": { <name>: <value>, ..., }
+ }
+ \endverbatim
+ *
+ * \param speech The speech engine
+ * \param format The format codec to use
+ *
+ * \returns 0 on success, -1 on error
+ */
+static int speech_aeap_engine_create(struct ast_speech *speech, struct ast_format *format)
+{
+	struct ast_aeap *aeap;
+	struct ast_variable *vars;
+	struct ast_json *json;
+
+	aeap = ast_aeap_create_and_connect_by_id(
+		speech->engine->name, &speech_aeap_params, CONNECTION_TIMEOUT);
+	if (!aeap) {
+		return -1;
+	}
+
+	speech->data = aeap;
+
+	/* Don't allow unloading of this module while an external application is in use */
+	ast_module_ref(ast_module_info->self);
+
+	vars = ast_aeap_custom_fields_get(speech->engine->name);
+
+	/* While the protocol allows sending of codec attributes, for now don't */
+	json = ast_json_pack("{s:s,s:[{s:s}],s:o*}", "version", SPEECH_AEAP_VERSION, "codecs",
+		"name", ast_format_get_codec_name(format), "params", custom_fields_to_params(vars));
+
+	ast_variables_destroy(vars);
+
+	if (ast_aeap_user_data_register(aeap, "speech", speech, NULL)) {
+		ast_module_unref(ast_module_info->self);
+		return -1;
+	}
+
+	/* send_request handles json ref */
+	if (speech_aeap_send_request(speech->data, "setup", json, format)) {
+		ast_module_unref(ast_module_info->self);
+		return -1;
+	}
+
+	/*
+	 * Add a reference to the engine here, so if it happens to get unregistered
+	 * while executing it won't disappear.
+	 */
+	ao2_ref(speech->engine, 1);
+
+	return 0;
+}
+
+static int speech_aeap_engine_destroy(struct ast_speech *speech)
+{
+	ao2_ref(speech->engine, -1);
+	ao2_cleanup(speech->data);
+
+	ast_module_unref(ast_module_info->self);
+
+	return 0;
+}
+
+static int speech_aeap_engine_write(struct ast_speech *speech, void *data, int len)
+{
+	return ast_aeap_send_binary(speech->data, data, len);
+}
+
+static int speech_aeap_engine_dtmf(struct ast_speech *speech, const char *dtmf)
+{
+	return speech_aeap_set(speech, "dtmf", dtmf);
+}
+
+static int speech_aeap_engine_start(struct ast_speech *speech)
+{
+	ast_speech_change_state(speech, AST_SPEECH_STATE_READY);
+
+	return 0;
+}
+
+static int speech_aeap_engine_change(struct ast_speech *speech, const char *name, const char *value)
+{
+	return speech_aeap_set(speech, name, value);
+}
+
+static int speech_aeap_engine_get_setting(struct ast_speech *speech, const char *name,
+	char *buf, size_t len)
+{
+	struct speech_setting setting = {
+		.param = name,
+		.len = len,
+		.buf = buf,
+	};
+
+	return speech_aeap_get(speech, name, &setting);
+}
+
+static int speech_aeap_engine_change_results_type(struct ast_speech *speech,
+	enum ast_speech_results_type results_type)
+{
+	return speech_aeap_set(speech, "results_type",
+		ast_speech_results_type_to_string(results_type));
+}
+
+static struct ast_speech_result *speech_aeap_engine_get(struct ast_speech *speech)
+{
+	struct ast_speech_result *results = NULL;
+
+	if (speech->results) {
+		return speech->results;
+	}
+
+	if (speech_aeap_get(speech, "results", &results)) {
+		return NULL;
+	}
+
+	return results;
+}
+
+static void speech_engine_destroy(void *obj)
+{
+	struct ast_speech_engine *engine = obj;
+
+	ao2_cleanup(engine->formats);
+	ast_free(engine->name);
+}
+
+static struct ast_speech_engine *speech_engine_alloc(const char *name)
+{
+	struct ast_speech_engine *engine;
+
+	engine = ao2_t_alloc_options(sizeof(*engine), speech_engine_destroy,
+		AO2_ALLOC_OPT_LOCK_NOLOCK, name);
+	if (!engine) {
+		ast_log(LOG_ERROR, "AEAP speech: unable create engine '%s'\n", name);
+		return NULL;
+	}
+
+	engine->name = ast_strdup(name);
+	if (!engine->name) {
+		ao2_ref(engine, -1);
+		return NULL;
+	}
+
+	engine->create = speech_aeap_engine_create;
+	engine->destroy = speech_aeap_engine_destroy;
+	engine->write = speech_aeap_engine_write;
+	engine->dtmf = speech_aeap_engine_dtmf;
+	engine->start = speech_aeap_engine_start;
+	engine->change = speech_aeap_engine_change;
+	engine->get_setting = speech_aeap_engine_get_setting;
+	engine->change_results_type = speech_aeap_engine_change_results_type;
+	engine->get = speech_aeap_engine_get;
+
+	engine->formats = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+
+	return engine;
+}
+
+static void speech_engine_alloc_and_register(const char *name, const struct ast_format_cap *formats)
+{
+	struct ast_speech_engine *engine;
+
+	engine = speech_engine_alloc(name);
+	if (!engine) {
+		return;
+	}
+
+	if (formats && ast_format_cap_append_from_cap(engine->formats,
+			formats, AST_MEDIA_TYPE_AUDIO)) {
+		ast_log(LOG_WARNING, "AEAP speech: Unable to add engine '%s' formats\n", name);
+		ao2_ref(engine, -1);
+		return;
+	}
+
+	if (ast_speech_register(engine)) {
+		ast_log(LOG_WARNING, "AEAP speech: Unable to register engine '%s'\n", name);
+		ao2_ref(engine, -1);
+	}
+}
+
+#ifdef TEST_FRAMEWORK
+
+static void speech_engine_alloc_and_register2(const char *name, const char *codec_names)
+{
+	struct ast_speech_engine *engine;
+
+	engine = speech_engine_alloc(name);
+	if (!engine) {
+		return;
+	}
+
+	if (codec_names && ast_format_cap_update_by_allow_disallow(engine->formats, codec_names, 1)) {
+		ast_log(LOG_WARNING, "AEAP speech: Unable to add engine '%s' codecs\n", name);
+		ao2_ref(engine, -1);
+		return;
+	}
+
+	if (ast_speech_register(engine)) {
+		ast_log(LOG_WARNING, "AEAP speech: Unable to register engine '%s'\n", name);
+		ao2_ref(engine, -1);
+	}
+}
+
+#endif
+
+static int unload_engine(void *obj, void *arg, int flags)
+{
+	if (ast_aeap_client_config_has_protocol(obj, SPEECH_PROTOCOL)) {
+		ao2_cleanup(ast_speech_unregister2(ast_sorcery_object_get_id(obj)));
+	}
+
+	return 0;
+}
+
+static int load_engine(void *obj, void *arg, int flags)
+{
+	const char *id;
+	const struct ast_format_cap *formats;
+	const struct ast_speech_engine *engine;
+
+	if (!ast_aeap_client_config_has_protocol(obj, SPEECH_PROTOCOL)) {
+		return 0;
+	}
+
+	id = ast_sorcery_object_get_id(obj);
+	formats = ast_aeap_client_config_codecs(obj);
+	if (!formats) {
+		formats = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+		if (!formats) {
+			ast_log(LOG_ERROR, "AEAP speech: unable to allocate default engine format for '%s'\n", id);
+			return 0;
+		}
+	}
+
+	engine = ast_speech_find_engine(id);
+	if (!engine) {
+		speech_engine_alloc_and_register(id, formats);
+		return 0;
+	}
+
+	if (ast_format_cap_identical(formats, engine->formats)) {
+		/* Same name, same formats then nothing changed */
+		return 0;
+	}
+
+	ao2_ref(ast_speech_unregister2(engine->name), -1);
+	speech_engine_alloc_and_register(id, formats);
+
+	return 0;
+}
+
+static int matches_engine(void *obj, void *arg, int flags)
+{
+	const struct ast_speech_engine *engine = arg;
+
+	return strcmp(ast_sorcery_object_get_id(obj), engine->name) ? 0 : CMP_MATCH;
+}
+
+static int should_unregister(const struct ast_speech_engine *engine, void *data)
+{
+	void *obj;
+
+	if (engine->create != speech_aeap_engine_create) {
+		/* Only want to potentially unregister AEAP speech engines */
+		return 0;
+	}
+
+#ifdef TEST_FRAMEWORK
+	if (!strcmp("_aeap_test_speech_", engine->name)) {
+		/* Don't remove the test engine */
+		return 0;
+	}
+#endif
+
+	obj = ao2_callback(data, 0, matches_engine, (void*)engine);
+
+	if (obj) {
+		ao2_ref(obj, -1);
+		return 0;
+	}
+
+	/* If no match in given container then unregister engine */
+	return 1;
+}
+
+static void speech_observer_loaded(const char *object_type)
+{
+	struct ao2_container *container;
+
+	if (strcmp(object_type, AEAP_CONFIG_CLIENT)) {
+		return;
+	}
+
+	container = ast_aeap_client_configs_get(SPEECH_PROTOCOL);
+	if (!container) {
+		return;
+	}
+
+	/*
+	 * An AEAP module reload has occurred. First
+	 * remove all engines that no longer exist.
+	 */
+	ast_speech_unregister_engines(should_unregister, container, __ao2_cleanup);
+
+	/* Now add or update engines */
+	ao2_callback(container, 0, load_engine, NULL);
+	ao2_ref(container, -1);
+}
+
+/*! \brief Observer for AEAP reloads */
+static const struct ast_sorcery_observer speech_observer = {
+	.loaded = speech_observer_loaded,
+};
+
+static int unload_module(void)
+{
+	struct ao2_container *container;
+
+#ifdef TEST_FRAMEWORK
+	ao2_cleanup(ast_speech_unregister2("_aeap_test_speech_"));
+#endif
+
+	ast_sorcery_observer_remove(ast_aeap_sorcery(), AEAP_CONFIG_CLIENT, &speech_observer);
+
+	container = ast_aeap_client_configs_get(SPEECH_PROTOCOL);
+	if (container) {
+		ao2_callback(container, 0, unload_engine, NULL);
+		ao2_ref(container, -1);
+	}
+
+	return 0;
+}
+
+static int load_module(void)
+{
+	struct ao2_container *container;
+
+	speech_aeap_params.msg_type = ast_aeap_message_type_json;
+
+	container = ast_aeap_client_configs_get(SPEECH_PROTOCOL);
+	if (container) {
+		ao2_callback(container, 0, load_engine, NULL);
+		ao2_ref(container, -1);
+	}
+
+	/*
+	 * Add an observer since a named speech server must be created,
+	 * registered, and eventually removed for all AEAP client
+	 * configuration matching the "speech_to_text" protocol.
+	*/
+	if (ast_sorcery_observer_add(ast_aeap_sorcery(), AEAP_CONFIG_CLIENT, &speech_observer)) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+#ifdef TEST_FRAMEWORK
+	speech_engine_alloc_and_register2("_aeap_test_speech_", "ulaw");
+#endif
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Asterisk External Application Speech Engine",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.load_pri = AST_MODPRI_CHANNEL_DEPEND,
+	.requires = "res_speech,res_aeap",
+);
diff --git a/res/res_stasis_snoop.c b/res/res_stasis_snoop.c
index 190ee06023030b1fe6d6a3626075301673d69923..12964cca008f9201c96d5f26f429bf54dc3c5c24 100644
--- a/res/res_stasis_snoop.c
+++ b/res/res_stasis_snoop.c
@@ -135,9 +135,13 @@ static void publish_chanspy_message(struct stasis_app_snoop *snoop, int start)
 	}
 	ast_multi_channel_blob_add_channel(payload, "spyer_channel", snoop_snapshot);
 
-	spyee_snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(snoop->spyee_chan));
-	if (spyee_snapshot) {
-		ast_multi_channel_blob_add_channel(payload, "spyee_channel", spyee_snapshot);
+	if (snoop->spyee_chan) {
+		ast_channel_lock(snoop->spyee_chan);
+		spyee_snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(snoop->spyee_chan));
+		ast_channel_unlock(snoop->spyee_chan);
+		if (spyee_snapshot) {
+			ast_multi_channel_blob_add_channel(payload, "spyee_channel", spyee_snapshot);
+		}
 	}
 
 	message = stasis_message_create(type, payload);
@@ -188,21 +192,9 @@ static struct ast_frame *snoop_read(struct ast_channel *chan)
 	}
 
 	ast_audiohook_lock(&snoop->spy);
-	if (snoop->spy_direction != AST_AUDIOHOOK_DIRECTION_BOTH) {
-		/*
-		 * When a singular direction is chosen frames are still written to the
-		 * opposing direction's queue. Those frames must be read so the queue
-		 * does not continue to grow, however since they are not needed for the
-		 * selected direction they can be dropped.
-		 */
-		enum ast_audiohook_direction opposing_direction =
-			snoop->spy_direction == AST_AUDIOHOOK_DIRECTION_READ ?
-			AST_AUDIOHOOK_DIRECTION_WRITE : AST_AUDIOHOOK_DIRECTION_READ;
-		ast_frame_dtor(ast_audiohook_read_frame(&snoop->spy, snoop->spy_samples,
-							opposing_direction, snoop->spy_format));
-	}
 
 	frame = ast_audiohook_read_frame(&snoop->spy, snoop->spy_samples, snoop->spy_direction, snoop->spy_format);
+
 	ast_audiohook_unlock(&snoop->spy);
 
 	return frame ? frame : &snoop->silence;
@@ -263,14 +255,8 @@ static struct ast_channel_tech snoop_tech = {
 static void *snoop_stasis_thread(void *obj)
 {
 	RAII_VAR(struct stasis_app_snoop *, snoop, obj, ao2_cleanup);
-	struct ast_app *stasis = pbx_findapp("Stasis");
-
-	if (!stasis) {
-		ast_hangup(snoop->chan);
-		return NULL;
-	}
 
-	pbx_exec(snoop->chan, stasis, ast_str_buffer(snoop->app));
+	ast_pbx_exec_application(snoop->chan, "Stasis", ast_str_buffer(snoop->app));
 
 	ast_hangup(snoop->chan);
 
@@ -293,6 +279,14 @@ static int snoop_setup_audiohook(struct ast_channel *chan, enum ast_audiohook_ty
 		return -1;
 	}
 
+	/* Set the audiohook direction so we don't write unnecessary frames */
+	if (ast_audiohook_set_frame_feed_direction(audiohook, *direction)) {
+		/* If we are unable to set direction, the audiohook either failed to init
+		   or someone else started using it already.  If we don't bail here, we risk
+		   feeding frames that will never be read */
+		return -1;
+	}
+
 	return ast_audiohook_attach(chan, audiohook);
 }
 
diff --git a/res/res_stir_shaken.c b/res/res_stir_shaken.c
index 157500a537ca71aa3ac0293d2d2be69eb3257cf2..efb8be957df4bd1b3b4faa55aa8e9c33cd10a977 100644
--- a/res/res_stir_shaken.c
+++ b/res/res_stir_shaken.c
@@ -403,7 +403,7 @@ int ast_stir_shaken_add_verification(struct ast_channel *chan, const char *ident
  */
 static void set_public_key_expiration(const char *public_cert_url, const struct curl_cb_data *data)
 {
-	char time_buf[32];
+	char time_buf[32], secs[AST_TIME_T_LEN];
 	char *value;
 	struct timeval actual_expires = ast_tvnow();
 	char hash[41];
@@ -441,7 +441,9 @@ static void set_public_key_expiration(const char *public_cert_url, const struct
 		actual_expires.tv_sec += EXPIRATION_BUFFER;
 	}
 
-	snprintf(time_buf, sizeof(time_buf), "%30lu", actual_expires.tv_sec);
+	ast_time_t_to_string(actual_expires.tv_sec, secs, sizeof(secs));
+
+	snprintf(time_buf, sizeof(time_buf), "%30s", secs);
 
 	ast_db_put(hash, "expiration", time_buf);
 }
@@ -602,6 +604,7 @@ static int stir_shaken_verify_signature(const char *msg, const char *signature,
  *
  * \param public_cert_url The public cert URL
  * \param path The path to download the file to
+ * \param acl The ACL to use for cURL (if not NULL)
  *
  * \retval NULL on failure
  * \retval full path filename on success
@@ -639,6 +642,7 @@ static char *run_curl(const char *public_cert_url, const char *path, const struc
  * \param public_cert_url The public cert URL
  * \param path The path to download the file to
  * \param curl Flag signaling if we have run CURL or not
+ * \param acl The ACL to use for cURL (if not NULL)
  *
  * \retval NULL on failure
  * \retval full path filename on success
@@ -1224,7 +1228,8 @@ struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json)
 	tmp_json = ast_json_object_get(json, "header");
 	header = ast_json_dump_string(tmp_json);
 	tmp_json = ast_json_object_get(json, "payload");
-	payload = ast_json_dump_string(tmp_json);
+
+	payload = ast_json_dump_string_sorted(tmp_json);
 	msg_len = strlen(header) + strlen(payload) + 2;
 	msg = ast_calloc(1, msg_len);
 	if (!msg) {
@@ -1657,7 +1662,7 @@ AST_TEST_DEFINE(test_stir_shaken_verify)
 	tmp_json = ast_json_object_get(json, "header");
 	header = ast_json_dump_string(tmp_json);
 	tmp_json = ast_json_object_get(json, "payload");
-	payload = ast_json_dump_string(tmp_json);
+	payload = ast_json_dump_string_sorted(tmp_json);
 
 	/* Test empty header parameter */
 	returned_payload = ast_stir_shaken_verify("", payload, (const char *)signed_payload->signature,
diff --git a/res/res_tonedetect.c b/res/res_tonedetect.c
index 4943f69fdc1686fa9538c5d0ba41420e0017b89e..c81e80c370d77832f347f611bbfe0ebec876cff7 100644
--- a/res/res_tonedetect.c
+++ b/res/res_tonedetect.c
@@ -46,6 +46,11 @@
 
 /*** DOCUMENTATION
 	<application name="WaitForTone" language="en_US">
+		<since>
+			<version>16.21.0</version>
+			<version>18.7.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Wait for tone
 		</synopsis>
@@ -94,6 +99,11 @@
 		</see-also>
 	</application>
 	<application name="ToneScan" language="en_US">
+		<since>
+			<version>16.23.0</version>
+			<version>18.9.0</version>
+			<version>19.1.0</version>
+		</since>
 		<synopsis>
 			Wait for period of time while scanning for call progress tones
 		</synopsis>
@@ -169,6 +179,11 @@
 		</see-also>
 	</application>
 	<function name="TONE_DETECT" language="en_US">
+		<since>
+			<version>16.21.0</version>
+			<version>18.7.0</version>
+			<version>19.0.0</version>
+		</since>
 		<synopsis>
 			Asynchronously detects a tone
 		</synopsis>
@@ -213,6 +228,10 @@
 						provided timeout) before going to the destination provided in the <literal>g</literal>
 						or <literal>h</literal> option. Default is 1.</para>
 					</option>
+					<option name="p">
+						<para>Match immediately on audible ringback tone, instead of or in addition to
+						a particular frequency.</para>
+					</option>
 					<option name="r">
 						<para>Apply to received frames only. Default is both directions.</para>
 					</option>
@@ -282,6 +301,7 @@ enum td_opts {
 	OPT_SIT = (1 << 9),
 	OPT_BUSY = (1 << 10),
 	OPT_DIALTONE = (1 << 11),
+	OPT_RINGING = (1 << 12),
 };
 
 enum {
@@ -301,6 +321,7 @@ AST_APP_OPTIONS(td_opts, {
 	AST_APP_OPTION_ARG('g', OPT_GOTO_RX, OPT_ARG_GOTO_RX),
 	AST_APP_OPTION_ARG('h', OPT_GOTO_TX, OPT_ARG_GOTO_TX),
 	AST_APP_OPTION_ARG('n', OPT_HITS_REQ, OPT_ARG_HITS_REQ),
+	AST_APP_OPTION('p', OPT_RINGING),
 	AST_APP_OPTION('s', OPT_SQUELCH),
 	AST_APP_OPTION('t', OPT_TX),
 	AST_APP_OPTION('r', OPT_RX),
@@ -352,7 +373,7 @@ static int detect_callback(struct ast_audiohook *audiohook, struct ast_channel *
 		return 0;
 	}
 
-	if (!(direction == AST_AUDIOHOOK_DIRECTION_READ ? &di->rx : &di->tx)) {
+	if (!(direction == AST_AUDIOHOOK_DIRECTION_READ ? di->rx : di->tx)) {
 		return 0;
 	}
 
@@ -388,18 +409,31 @@ static int detect_callback(struct ast_audiohook *audiohook, struct ast_channel *
 		if (tstate > 0) {
 			ast_debug(3, "tcount: %d, tstate: %d\n", tcount, tstate);
 			switch (tstate) {
+			case DSP_TONE_STATE_RINGING:
+				if (di->signalfeatures & DSP_PROGRESS_RINGING) {
+					ast_debug(1, "Detected ringing on %s in %s direction\n", ast_channel_name(chan),
+						direction == AST_AUDIOHOOK_DIRECTION_READ ? "read" : "write");
+					match = 1;
+				}
+				break;
 			case DSP_TONE_STATE_DIALTONE:
 				if (di->signalfeatures & DSP_FEATURE_WAITDIALTONE) {
+					ast_debug(1, "Detected dial tone on %s in %s direction\n", ast_channel_name(chan),
+						direction == AST_AUDIOHOOK_DIRECTION_READ ? "read" : "write");
 					match = 1;
 				}
 				break;
 			case DSP_TONE_STATE_BUSY:
 				if (di->signalfeatures & DSP_PROGRESS_BUSY) {
+					ast_debug(1, "Detected busy tone on %s in %s direction\n", ast_channel_name(chan),
+						direction == AST_AUDIOHOOK_DIRECTION_READ ? "read" : "write");
 					match = 1;
 				}
 				break;
 			case DSP_TONE_STATE_SPECIAL3:
 				if (di->signalfeatures & DSP_PROGRESS_CONGESTION) {
+					ast_debug(1, "Detected SIT on %s in %s direction\n", ast_channel_name(chan),
+						direction == AST_AUDIOHOOK_DIRECTION_READ ? "read" : "write");
 					match = 1;
 				}
 				break;
@@ -412,7 +446,8 @@ static int detect_callback(struct ast_audiohook *audiohook, struct ast_channel *
 				} else if (di->gototx) {
 					ast_async_parseable_goto(chan, di->gototx);
 				} else {
-					ast_debug(3, "Detected call progress signal, but don't know where to go\n");
+					ast_debug(3, "Detected call progress signal in %s direction, but don't know where to go\n",
+						direction == AST_AUDIOHOOK_DIRECTION_READ ? "read" : "write");
 				}
 			}
 		}
@@ -568,6 +603,9 @@ static int parse_signal_features(struct ast_flags *flags)
 	if (ast_test_flag(flags, OPT_DIALTONE)) {
 		features |= DSP_FEATURE_WAITDIALTONE;
 	}
+	if (ast_test_flag(flags, OPT_RINGING)) {
+		features |= DSP_PROGRESS_RINGING;
+	}
 
 	return features;
 }
@@ -887,7 +925,7 @@ static int scan_exec(struct ast_channel *chan, const char *data)
 	}
 	ast_dsp_set_features(dsp, features);
 	/* all modems begin negotiating with Bell 103. An answering modem just sends mark tone, or 2225 Hz */
-	ast_dsp_set_freqmode(dsp, 2225, 400, 16, 0); /* this needs to be pretty short, or the progress tones code will thing this is voice */
+	ast_dsp_set_freqmode(dsp, 2225, 400, 16, 0); /* this needs to be pretty short, or the progress tones code will think this is voice */
 
 	if (fax) { /* fax detect uses same tone detect internals as modem and causes things to not work as intended, so use a separate DSP if needed. */
 		ast_dsp_set_features(dsp2, DSP_FEATURE_FAX_DETECT); /* fax tone */
@@ -925,8 +963,8 @@ static int scan_exec(struct ast_channel *chan, const char *data)
 				} else if (fax) {
 					char result;
 					frame2 = ast_dsp_process(chan, dsp2, frame2);
-					result = frame->subclass.integer;
-					if (result == AST_FRAME_DTMF) {
+					result = frame2->subclass.integer;
+					if (frame2->frametype == AST_FRAME_DTMF) {
 						if (result == 'e') {
 							pbx_builtin_setvar_helper(chan, "TONESCANSTATUS", "FAX");
 							match = 1;
diff --git a/res/stasis_recording/stored.c b/res/stasis_recording/stored.c
index 6cdca96e2bb8990342f86945293cb78c84517c6c..9e9ae58343302f7b30709f4e3418bcfc2f411cd7 100644
--- a/res/stasis_recording/stored.c
+++ b/res/stasis_recording/stored.c
@@ -130,6 +130,7 @@ static int split_path(const char *path, char **dir, char **file)
 
 struct match_recording_data {
 	const char *file;
+	size_t length;
 	char *file_with_ext;
 };
 
@@ -160,7 +161,9 @@ static int handle_find_recording(const char *dir_name, const char *filename, voi
 	int num;
 
 	/* If not a recording or the names do not match the keep searching */
-	if (!(num = is_recording(filename)) || strncmp(data->file, filename, num)) {
+	if (!(num = is_recording(filename))
+	   || data->length != num
+	   || strncmp(data->file, filename, num)) {
 		return 0;
 	}
 
@@ -186,6 +189,7 @@ static char *find_recording(const char *dir_name, const char *file)
 {
 	struct match_recording_data data = {
 		.file = file,
+		.length = strlen(file),
 		.file_with_ext = NULL
 	};
 
diff --git a/rest-api-templates/make_ari_stubs.py b/rest-api-templates/make_ari_stubs.py
index 9a340d3fb2df875b640325a2b1381228a983a1ef..cc0407a2a5299be15d07dc427e9d27305608d214 100755
--- a/rest-api-templates/make_ari_stubs.py
+++ b/rest-api-templates/make_ari_stubs.py
@@ -42,7 +42,7 @@ def rel(file):
     """
     return os.path.join(TOPDIR, file)
 
-WIKI_PREFIX = 'Asterisk 18'
+WIKI_PREFIX = 'Asterisk 20'
 
 API_TRANSFORMS = [
     Transform(rel('api.wiki.mustache'),
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
index 04d1577337f3213efd9e4ff82ff9d73e39566ec8..269976dfa4bc20b54b00691c77b5786d8a5b3b46 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -2126,6 +2126,11 @@
 					"type": "string",
 					"description": "Unique identifier of the channel.\n\nThis is the same as the Uniqueid field in AMI."
 				},
+				"protocol_id": {
+					"required": true,
+					"type": "string",
+					"description": "Protocol id from underlying channel driver (i.e. Call-ID for chan_sip/chan_pjsip; will be empty if not applicable or not implemented by driver)."
+				},
 				"name": {
 					"required": true,
 					"type": "string",
diff --git a/rest-api/resources.json b/rest-api/resources.json
index 6de3f523b93f6dea74453456d471777596c7daf9..44a61118039ed457eca06e0cedd0b848dcb10d76 100644
--- a/rest-api/resources.json
+++ b/rest-api/resources.json
@@ -2,7 +2,7 @@
 	"_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.",
 	"_author": "David M. Lee, II <dlee@digium.com>",
 	"_svn_revision": "$Revision$",
-	"apiVersion": "6.0.0",
+	"apiVersion": "8.0.0",
 	"swaggerVersion": "1.1",
 	"basePath": "http://localhost:8088/ari",
 	"apis": [
diff --git a/tests/CI/buildAsterisk.sh b/tests/CI/buildAsterisk.sh
index 5f1597669e43e25f8aeddebffb6fe1c86e5a941c..6d06f05159accbf4fb0fbfee369200ad82a84990 100755
--- a/tests/CI/buildAsterisk.sh
+++ b/tests/CI/buildAsterisk.sh
@@ -144,19 +144,19 @@ if [ $NO_MENUSELECT -eq 0 ] ; then
 	fi
 	runner menuselect/menuselect `gen_cats enable $cat_enables` menuselect.makeopts
 
-	mod_disables="res_digium_phone chan_vpb"
+	mod_disables="res_digium_phone"
 	if [ $TESTED_ONLY -eq 1 ] ; then
 		# These modules are not tested at all.  They are loaded but nothing is ever done
 		# with them, no testsuite tests depend on them.
 		mod_disables+=" app_adsiprog app_alarmreceiver app_celgenuserevent app_db app_dictate"
-		mod_disables+=" app_dumpchan app_externalivr app_festival app_getcpeid app_ices app_image"
-		mod_disables+=" app_jack app_milliwatt app_minivm app_morsecode app_mp3 app_nbscat app_privacy"
-		mod_disables+=" app_readexten app_sms app_speech_utils app_test app_url app_waitforring"
+		mod_disables+=" app_dumpchan app_externalivr app_festival app_getcpeid"
+		mod_disables+=" app_jack app_milliwatt app_minivm app_morsecode app_mp3 app_privacy"
+		mod_disables+=" app_readexten app_sms app_speech_utils app_test app_waitforring"
 		mod_disables+=" app_waitforsilence app_waituntil app_zapateller"
 		mod_disables+=" cdr_adaptive_odbc cdr_custom cdr_manager cdr_odbc cdr_pgsql cdr_radius"
-		mod_disables+=" cdr_syslog cdr_tds"
+		mod_disables+=" cdr_tds"
 		mod_disables+=" cel_odbc cel_pgsql cel_radius cel_sqlite3_custom cel_tds"
-		mod_disables+=" chan_alsa chan_console chan_mgcp chan_motif chan_oss chan_rtp chan_skinny chan_unistim"
+		mod_disables+=" chan_alsa chan_console chan_mgcp chan_motif chan_rtp chan_skinny chan_unistim"
 		mod_disables+=" func_frame_trace func_pitchshift func_speex func_volume func_dialgroup"
 		mod_disables+=" func_periodic_hook func_sprintf func_enum func_extstate func_sysinfo func_iconv"
 		mod_disables+=" func_callcompletion func_version func_rand func_sha1 func_module func_md5"
diff --git a/tests/CI/installAsterisk.sh b/tests/CI/installAsterisk.sh
index 82397723a505da649e95a0d6a5e2c8e9f14febde..da48bc2a6c5cc2a8d69bb8fb3ac19ac7b42015b6 100755
--- a/tests/CI/installAsterisk.sh
+++ b/tests/CI/installAsterisk.sh
@@ -33,6 +33,7 @@ fi
 
 set +e
 if [ x"$USER_GROUP" != x ] ; then
+	chown -R $USER_GROUP $DESTDIR/var/cache/asterisk
 	chown -R $USER_GROUP $DESTDIR/var/lib/asterisk
 	chown -R $USER_GROUP $DESTDIR/var/spool/asterisk
 	chown -R $USER_GROUP $DESTDIR/var/log/asterisk
diff --git a/tests/CI/runTestsuite.sh b/tests/CI/runTestsuite.sh
index 466991a44c53f9caef6615db8c06e770472828f0..ec4292c72c5d48b2108c06d1dab194b47a109b21 100755
--- a/tests/CI/runTestsuite.sh
+++ b/tests/CI/runTestsuite.sh
@@ -11,16 +11,25 @@ if [ x"$WORK_DIR" != x ] ; then
 fi
 
 pushd $TESTSUITE_DIR
-
 ./cleanup-test-remnants.sh
 
 if [ $REALTIME -eq 1 ] ; then
 	$CIDIR/setupRealtime.sh --initialize-db=${INITIALIZE_DB:?0}
 fi
 
-export PYTHONPATH=./lib/python/
+# check to see if venv scripts exist so we can use them
+if [ -f ./setupVenv.sh ] ; then
+	echo "Running in Virtual Environment"
+	# explicitly invoking setupVenv to capture output in case of failure
+	./setupVenv.sh
+	VENVPREFIX="runInVenv.sh python "
+else
+	echo "Running in Legacy Mode"
+	export PYTHONPATH=./lib/python/
+fi
+
 echo "Running tests ${TESTSUITE_COMMAND} ${AST_WORK_DIR:+with work directory ${AST_WORK_DIR}}"
-./runtests.py --cleanup --timeout=${TEST_TIMEOUT} ${TESTSUITE_COMMAND} | contrib/scripts/pretty_print --no-color --no-timer --term-width=120 --show-errors || :
+./${VENVPREFIX}runtests.py --cleanup --timeout=${TEST_TIMEOUT} ${TESTSUITE_COMMAND} | contrib/scripts/pretty_print --no-color --no-timer --term-width=120 --show-errors || :
 
 if [ $REALTIME -eq 1 ] ; then
 	$CIDIR/teardownRealtime.sh --cleanup-db=${CLEANUP_DB:?0}
diff --git a/tests/CI/runUnittests.sh b/tests/CI/runUnittests.sh
index e2d7e45a7ed959adbefeb2b76da62f95087194cd..b929c542b0501e61d2acba02f34a2b43757136a4 100755
--- a/tests/CI/runUnittests.sh
+++ b/tests/CI/runUnittests.sh
@@ -52,7 +52,7 @@ run_tests_socket() {
 
 # If DESTDIR is used to install and run asterisk from non standard locations,
 # the directory entries in asterisk.conf need to be munged to prepend DESTDIR.
-ALTERED=$(head -10 ../tmp/DESTDIR/etc/asterisk/asterisk.conf | grep -q "DESTDIR" && echo yes)
+ALTERED=$(head -10 "$ASTETCDIR/asterisk.conf" | grep -q "DESTDIR" && echo yes)
 if [ x"$ALTERED" = x ] ; then
 	# In the section that starts with [directories and ends with a blank line,
 	# replace "=> " with "=> ${DESTDIR}"
@@ -119,7 +119,7 @@ else
 fi
 
 # Cleanup "just in case"
-sudo killall -qe -ABRT $ASTERISK 
+sudo killall -qe -ABRT $ASTERISK
 
 runner rsync -vaH $DESTDIR/var/log/asterisk/. $OUTPUTDIR
 set +x
@@ -128,11 +128,11 @@ set +x
 
 for core in $(asterisk_corefile_glob)
 do
-	if [ -f $core ]
+	if [ -f "$core" ] && [ "${core##*.}" != "txt" ]
 	then
 		echo "*** Found a core file ($core) after running unit tests ***"
 		set -x
-		sudo OUTPUTDIR=$OUTPUTDIR $DESTDIR/var/lib/asterisk/scripts/ast_coredumper --no-default-search $core
+		sudo $DESTDIR/var/lib/asterisk/scripts/ast_coredumper --outputdir=$OUTPUTDIR --no-default-search $core
 	fi
 done
 
diff --git a/tests/Makefile b/tests/Makefile
index 715c3f859387ff8dd4a9b777ba7ada0c0646c77a..13f885547f3881d095e58c5f73e46e6ca4443851 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -20,5 +20,8 @@ all: _all
 include $(ASTTOPDIR)/Makefile.moddir_rules
 
 test_astobj2.o: _ASTCFLAGS+=$(call get_menuselect_cflags,AO2_DEBUG)
+# can't use '%y' in strftime() without warnings since it's not y2k compliant
+test_capture.o: _ASTCFLAGS+=$(AST_NO_FORMAT_Y2K)
+test_crypto.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
 test_strings.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) $(AST_NO_STRINGOP_TRUNCATION)
 test_voicemail_api.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
diff --git a/tests/keys/rsa_key1.key b/tests/keys/rsa_key1.key
new file mode 100644
index 0000000000000000000000000000000000000000..117a4e93c5a77541ccdc2c3dbe9b64c58ee7078c
--- /dev/null
+++ b/tests/keys/rsa_key1.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQC206PN7hvmoc0p1urAeKozmUha/h3KIAIO4DG5Muz6x3Zribdx
+cKfgmw28FwamAGT1n0y1+qGkL1vyHY4YMDjHVVSLB8h5Je89UxgXxl/PUpSx4kFN
+gZofk28Mx1lG2aLEBHXFNhrjZbdfZzeljZHYfrsLf9nxQvYeA0W2YJ3g1wIDAQAB
+AoGBAJ2V9OYmrAPySS4cIoI+P650G+raiIDVcBC0bAeO/rb2QHtW3Di6euldnMwY
+KNHjGyKf6XYeDz++1ojtsrHktrqcaXfh9J1qpxXXGxMZww00so+VOrhCbs0uf6Yh
+FdZ1Dc3UsBLhrA/fBaaw3xRwFvsgnxmJPX6R/gmC+A6uc/QxAkEA5z9TBbdW6bsA
+SPCmUOmSalX9WyGrbaZwkvCBtuKCfHzKUcxdbXw8e68GralzGITwU3XcYn/mVqk0
+ztfBWNt+fwJBAMplfFU7uPDZwfjC3eXXljxaSzoA7EzLcByslYLuAJMYKITQOiv0
+KBb+zJxvTntArF5TOkCeVYUMZKcL8HEXIakCQFaOwnHKTZMRdyrWQTraIv8AjuQU
+t0lE2rB1q+gb4wHb6BM0Luhzb2RQgGxyl+1enWJwJH0OKNbZYTXnVqz/A9sCQFME
+4cUMZEXW7GufcumOTr+ewfCe5E5zvB7m48T63x128VfZGaNh2PfluAQK3AROeOWP
++fr7d1TFypuCmDOrK1ECQH1CeBWxVRx695uYmsAYwX8FNIn0agFasdk7wGUyP7ow
+idIaA92AHJ1gQXbEyh4iDrZZdh5fopg8sxRXdFfouFo=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/keys/rsa_key1.pub b/tests/keys/rsa_key1.pub
new file mode 100644
index 0000000000000000000000000000000000000000..d25a2e4703f3cfc4def41695c15c659183df7c2a
--- /dev/null
+++ b/tests/keys/rsa_key1.pub
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC206PN7hvmoc0p1urAeKozmUha
+/h3KIAIO4DG5Muz6x3ZribdxcKfgmw28FwamAGT1n0y1+qGkL1vyHY4YMDjHVVSL
+B8h5Je89UxgXxl/PUpSx4kFNgZofk28Mx1lG2aLEBHXFNhrjZbdfZzeljZHYfrsL
+f9nxQvYeA0W2YJ3g1wIDAQAB
+-----END PUBLIC KEY-----
diff --git a/tests/test_aeap.c b/tests/test_aeap.c
new file mode 100644
index 0000000000000000000000000000000000000000..899da7a75879740a3a0fec079f48256d7842c73c
--- /dev/null
+++ b/tests/test_aeap.c
@@ -0,0 +1,252 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<depend>res_aeap</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/test.h"
+#include "asterisk/module.h"
+#include "asterisk/file.h"
+#include "asterisk/http_websocket.h"
+#include "asterisk/json.h"
+
+#include "asterisk/res_aeap.h"
+#include "asterisk/res_aeap_message.h"
+
+#define CATEGORY "/res/aeap/"
+
+#define ADDR "127.0.0.1:8088"
+#define AEAP_TRANSPORT_TYPE "ws"
+#define AEAP_REMOTE_URL "ws://" ADDR "/ws"
+#define AEAP_REMOTE_PROTOCOL "echo"
+#define AEAP_MESSAGE_ID "foo"
+#define AEAP_CONNECTION_TIMEOUT 2000
+
+AST_TEST_DEFINE(create_and_connect)
+{
+	RAII_VAR(struct ast_aeap *, aeap, NULL, ao2_cleanup);
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->explicit_only = 0;
+		info->category = CATEGORY;
+		info->summary = "test creating and connecting to an AEAP application";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_validate(test, (aeap = ast_aeap_create_and_connect(AEAP_TRANSPORT_TYPE,
+		NULL, AEAP_REMOTE_URL, AEAP_REMOTE_PROTOCOL, AEAP_CONNECTION_TIMEOUT)));
+
+	return AST_TEST_PASS;
+}
+
+static void handle_string(struct ast_aeap *aeap, const char *buf, intmax_t size)
+{
+	int *passed = ast_aeap_user_data_object_by_id(aeap, AEAP_MESSAGE_ID);
+
+	if (strstr(buf, AEAP_MESSAGE_ID)) {
+		++*passed;
+	}
+}
+
+static void handle_timeout(struct ast_aeap *aeap, struct ast_aeap_message *message, void *data)
+{
+	int *passed = ast_aeap_user_data_object_by_id(aeap, AEAP_MESSAGE_ID);
+
+	++*passed;
+}
+
+AST_TEST_DEFINE(send_msg_handle_string)
+{
+	int passed = 0;
+	RAII_VAR(struct ast_aeap *, aeap, NULL, ao2_cleanup);
+	struct ast_aeap_tsx_params tsx_params = {0};
+	struct ast_aeap_params aeap_params = {
+		.on_string = handle_string,
+	};
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->explicit_only = 0;
+		info->category = CATEGORY;
+		info->summary = "test an AEAP application string handler";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	tsx_params.timeout = 2000; /* Test will end by timing out */
+	tsx_params.on_timeout = handle_timeout;
+	tsx_params.wait = 1;
+
+	ast_test_validate(test, (aeap = ast_aeap_create_and_connect(AEAP_TRANSPORT_TYPE,
+		&aeap_params, AEAP_REMOTE_URL, AEAP_REMOTE_PROTOCOL, AEAP_CONNECTION_TIMEOUT)));
+
+	ast_test_validate(test, (!ast_aeap_user_data_register(aeap, AEAP_MESSAGE_ID, &passed, NULL)));
+	ast_test_validate(test, (tsx_params.msg = ast_aeap_message_create_request(
+		ast_aeap_message_type_json, "foo", AEAP_MESSAGE_ID, NULL)));
+	ast_test_validate(test, ast_aeap_send_msg_tsx(aeap, &tsx_params)); /* Returns fail on timeout */
+	ast_aeap_user_data_unregister(aeap, AEAP_MESSAGE_ID);
+
+	return passed == 2 ? AST_TEST_PASS : AST_TEST_FAIL;
+}
+
+static int handle_msg(struct ast_aeap *aeap, struct ast_aeap_message *message, void *data)
+{
+	int *passed = ast_aeap_user_data_object_by_id(aeap, AEAP_MESSAGE_ID);
+
+	*passed = !strcmp(ast_aeap_message_id(message), AEAP_MESSAGE_ID) &&
+		ast_aeap_message_is_named(message, data);
+
+	if (!*passed) {
+		ast_log(LOG_ERROR, "Name '%s' did not equal '%s' for message '%s'",
+			ast_aeap_message_name(message), (char *)data, ast_aeap_message_id(message));
+	}
+
+	return 0;
+}
+
+static const struct ast_aeap_message_handler handlers[] = {
+	{ "foo", handle_msg },
+};
+
+AST_TEST_DEFINE(send_msg_handle_response)
+{
+	int passed = 0;
+	RAII_VAR(struct ast_aeap *, aeap, NULL, ao2_cleanup);
+	char *name = "foo";
+	struct ast_aeap_params aeap_params = {
+		.response_handlers = handlers,
+		.response_handlers_size = ARRAY_LEN(handlers),
+	};
+	struct ast_aeap_tsx_params tsx_params = {0};
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->explicit_only = 0;
+		info->category = CATEGORY;
+		info->summary = "test an AEAP application response handler";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	aeap_params.msg_type = ast_aeap_message_type_json;
+
+	tsx_params.timeout = 2000;
+	tsx_params.wait = 1;
+	tsx_params.obj = name;
+
+	ast_test_validate(test, (aeap = ast_aeap_create_and_connect(AEAP_TRANSPORT_TYPE,
+		&aeap_params, AEAP_REMOTE_URL, AEAP_REMOTE_PROTOCOL, AEAP_CONNECTION_TIMEOUT)));
+	ast_test_validate(test, (!ast_aeap_user_data_register(aeap, AEAP_MESSAGE_ID, &passed, NULL)));
+	ast_test_validate(test, (tsx_params.msg = ast_aeap_message_create_response(
+		ast_aeap_message_type_json, name, AEAP_MESSAGE_ID, NULL)));
+	ast_test_validate(test, !ast_aeap_send_msg_tsx(aeap, &tsx_params));
+	ast_aeap_user_data_unregister(aeap, AEAP_MESSAGE_ID);
+
+	return passed ? AST_TEST_PASS : AST_TEST_FAIL;
+}
+
+AST_TEST_DEFINE(send_msg_handle_request)
+{
+	int passed = 0;
+	RAII_VAR(struct ast_aeap *, aeap, NULL, ao2_cleanup);
+	char *name = "foo";
+	struct ast_aeap_params aeap_params = {
+		.request_handlers = handlers,
+		.request_handlers_size = ARRAY_LEN(handlers),
+	};
+	struct ast_aeap_tsx_params tsx_params = {0};
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->explicit_only = 0;
+		info->category = CATEGORY;
+		info->summary = "test an AEAP application request handler";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	aeap_params.msg_type = ast_aeap_message_type_json;
+
+	tsx_params.timeout = 2000;
+	tsx_params.wait = 1;
+	tsx_params.obj = name;
+
+	ast_test_validate(test, (aeap = ast_aeap_create_and_connect(AEAP_TRANSPORT_TYPE,
+		&aeap_params, AEAP_REMOTE_URL, AEAP_REMOTE_PROTOCOL, AEAP_CONNECTION_TIMEOUT)));
+	ast_test_validate(test, (!ast_aeap_user_data_register(aeap, AEAP_MESSAGE_ID, &passed, NULL)));
+	ast_test_validate(test, (tsx_params.msg = ast_aeap_message_create_request(
+		ast_aeap_message_type_json, name, AEAP_MESSAGE_ID, NULL)));
+	ast_test_validate(test, !ast_aeap_send_msg_tsx(aeap, &tsx_params));
+	ast_aeap_user_data_unregister(aeap, AEAP_MESSAGE_ID);
+
+	return passed ? AST_TEST_PASS : AST_TEST_FAIL;
+}
+
+static struct ast_http_server *http_server;
+
+static int load_module(void)
+{
+	if (!(http_server = ast_http_test_server_get("aeap transport http server", NULL))) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	AST_TEST_REGISTER(create_and_connect);
+	AST_TEST_REGISTER(send_msg_handle_string);
+	AST_TEST_REGISTER(send_msg_handle_response);
+	AST_TEST_REGISTER(send_msg_handle_request);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	AST_TEST_UNREGISTER(send_msg_handle_request);
+	AST_TEST_UNREGISTER(send_msg_handle_response);
+	AST_TEST_UNREGISTER(send_msg_handle_string);
+	AST_TEST_UNREGISTER(create_and_connect);
+
+	ast_http_test_server_discard(http_server);
+
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asterisk External Application Protocol Object Tests",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.requires = "res_aeap",
+);
diff --git a/tests/test_aeap_speech.c b/tests/test_aeap_speech.c
new file mode 100644
index 0000000000000000000000000000000000000000..2e658c1f3991c97ee37aedc45998438b35f97c7d
--- /dev/null
+++ b/tests/test_aeap_speech.c
@@ -0,0 +1,287 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<depend>res_aeap</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/test.h"
+#include "asterisk/module.h"
+#include "asterisk/file.h"
+#include "asterisk/format_cap.h"
+#include "asterisk/http.h"
+#include "asterisk/http_websocket.h"
+#include "asterisk/json.h"
+#include "asterisk/speech.h"
+
+#include "asterisk/res_aeap.h"
+#include "asterisk/res_aeap_message.h"
+
+#define ADDR "127.0.0.1:8088"
+
+static int speech_test_server_setup(struct ast_json *req, struct ast_json *resp)
+{
+	struct ast_json *params;
+
+	if (ast_json_object_set(resp, "codecs", ast_json_ref(ast_json_object_get(req, "codecs")))) {
+		return -1;
+	}
+
+	params = ast_json_object_get(req, "params"); /* Optional */
+	if (params && ast_json_object_set(resp, "params", ast_json_ref(params))) {
+		return -1;
+	}
+
+	return 0;
+}
+
+#define TEST_SPEECH_RESULTS_TEXT "foo"
+#define TEST_SPEECH_RESULTS_SCORE 7
+#define TEST_SPEECH_RESULTS_GRAMMAR "bar"
+#define TEST_SPEECH_RESULTS_BEST 1
+
+static int speech_test_server_get(struct ast_json *req, struct ast_json *resp)
+{
+	const char *param;
+	struct ast_json *json = NULL;
+
+	param = ast_json_string_get(ast_json_array_get(ast_json_object_get(req, "params"), 0));
+	if (!param) {
+		return -1;
+	}
+
+	if (!strcmp(param, "results")) {
+		json = ast_json_pack("{s:[{s:s,s:i,s:s,s:i}]}",
+			param,
+			"text", TEST_SPEECH_RESULTS_TEXT,
+			"score", TEST_SPEECH_RESULTS_SCORE,
+			"grammar", TEST_SPEECH_RESULTS_GRAMMAR,
+			"best", TEST_SPEECH_RESULTS_BEST);
+	} else {
+		/* Assume setting */
+		json = ast_json_pack("{s:s}", param, "bar");
+	}
+
+	if (!json || ast_json_object_set(resp, "params", json)) {
+		return -1;
+	}
+
+	return 0;
+}
+
+static int speech_test_server_set(struct ast_json *req, struct ast_json *resp)
+{
+	if (ast_json_object_set(resp, "params", ast_json_ref(ast_json_object_get(req, "params")))) {
+		return -1;
+	}
+
+	return 0;
+}
+
+static int speech_test_server_handle_request(struct ast_websocket *ws, const void *buf, uint64_t size)
+{
+	struct ast_json *req;
+	struct ast_json *resp;
+	const char *name;
+	char *resp_buf;
+	int res = 0;
+
+	req = ast_json_load_buf(buf, size, NULL);
+	if (!req) {
+		ast_log(LOG_ERROR, "speech test handle request: unable to load json\n");
+		return -1;
+	}
+
+	name = ast_json_object_string_get(req, "request");
+	if (!name) {
+		ast_log(LOG_ERROR, "speech test handle request: no name\n");
+		ast_json_unref(req);
+		return -1;
+	}
+
+	resp = ast_json_pack("{s:s, s:s}", "response", name,
+		"id", ast_json_object_string_get(req, "id"));
+	if (!resp) {
+		ast_log(LOG_ERROR, "speech test handle request: unable to create response '%s'\n", name);
+		ast_json_unref(req);
+		return -1;
+	}
+
+	if (!strcmp(name, "setup")) {
+		res = speech_test_server_setup(req, resp);
+	} else if (!strcmp(name, "get")) {
+		res = speech_test_server_get(req, resp);
+	} else if (!strcmp(name, "set")) {
+		res = speech_test_server_set(req, resp);
+	} else {
+		ast_log(LOG_ERROR, "speech test handle request: unsupported request '%s'\n", name);
+		return -1;
+	}
+
+	if (res) {
+		ast_log(LOG_ERROR, "speech test handle request: unable to build response '%s'\n", name);
+		ast_json_unref(resp);
+		ast_json_unref(req);
+		return -1;
+	}
+
+	resp_buf = ast_json_dump_string(resp);
+	ast_json_unref(resp);
+
+	if (!resp_buf) {
+		ast_log(LOG_ERROR, "speech test handle request: unable to dump response '%s'\n", name);
+		ast_json_unref(req);
+		return -1;
+	}
+
+	res = ast_websocket_write_string(ws, resp_buf);
+	if (res) {
+		ast_log(LOG_ERROR, "speech test handle request: unable to write response '%s'\n", name);
+	}
+
+	ast_json_unref(req);
+	ast_free(resp_buf);
+
+	return res;
+}
+
+static void speech_test_server_cb(struct ast_websocket *ws, struct ast_variable *parameters,
+	struct ast_variable *headers)
+{
+	int res;
+
+	if (ast_fd_set_flags(ast_websocket_fd(ws), O_NONBLOCK)) {
+		ast_websocket_unref(ws);
+		return;
+	}
+
+	while ((res = ast_websocket_wait_for_input(ws, -1)) > 0) {
+		char *payload;
+		uint64_t payload_len;
+		enum ast_websocket_opcode opcode;
+		int fragmented;
+
+		if (ast_websocket_read(ws, &payload, &payload_len, &opcode, &fragmented)) {
+			ast_log(LOG_ERROR, "speech test: Read failure in server loop\n");
+			break;
+		}
+
+		switch (opcode) {
+			case AST_WEBSOCKET_OPCODE_CLOSE:
+				ast_websocket_unref(ws);
+				return;
+			case AST_WEBSOCKET_OPCODE_BINARY:
+				ast_websocket_write(ws, opcode, payload, payload_len);
+				break;
+			case AST_WEBSOCKET_OPCODE_TEXT:
+				ast_debug(3, "payload=%.*s\n", (int)payload_len, payload);
+				if (speech_test_server_handle_request(ws, payload, payload_len)) {
+					ast_websocket_unref(ws);
+					return;
+				}
+				break;
+			default:
+				break;
+		}
+	}
+	ast_websocket_unref(ws);
+}
+
+AST_TEST_DEFINE(res_speech_aeap_test)
+{
+	RAII_VAR(struct ast_format_cap *, cap, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_speech_result *, results, NULL, ast_speech_results_free);
+	struct ast_speech *speech = NULL;
+	enum ast_test_result_state res = AST_TEST_PASS;
+	char buf[8] = "";
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->explicit_only = 0;
+		info->category = "/res/aeap/speech/";
+		info->summary = "test the speech AEAP interface";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_validate(test, !ast_websocket_add_protocol("_aeap_test_speech_", speech_test_server_cb));
+
+	ast_test_validate(test, (cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)));
+	ast_test_validate(test, !ast_format_cap_update_by_allow_disallow(cap, "ulaw", 1));
+
+	ast_test_validate_cleanup(test, (speech = ast_speech_new("_aeap_test_speech_", cap)), res, cleanup);
+	ast_speech_start(speech);
+	ast_test_validate_cleanup(test, !ast_speech_dtmf(speech, "1"), res, cleanup);
+	ast_test_validate_cleanup(test, !ast_speech_change(speech, "foo", "bar"), res, cleanup);
+	ast_test_validate_cleanup(test, !ast_speech_change_results_type(
+		speech, AST_SPEECH_RESULTS_TYPE_NBEST), res, cleanup);
+
+	ast_test_validate_cleanup(test, !ast_speech_get_setting(
+		speech, "foo", buf, sizeof(buf)), res, cleanup);
+	ast_test_validate_cleanup(test, !strcmp(buf, "bar"), res, cleanup);
+
+	ast_test_validate_cleanup(test, (results = ast_speech_results_get(speech)), res, cleanup);
+	ast_test_validate_cleanup(test, !strcmp(results->text, TEST_SPEECH_RESULTS_TEXT), res, cleanup);
+	ast_test_validate_cleanup(test, results->score == TEST_SPEECH_RESULTS_SCORE, res, cleanup);
+	ast_test_validate_cleanup(test, !strcmp(results->grammar, TEST_SPEECH_RESULTS_GRAMMAR), res, cleanup);
+	ast_test_validate_cleanup(test, results->nbest_num == TEST_SPEECH_RESULTS_BEST, res, cleanup);
+
+cleanup:
+	if (speech) {
+		ast_speech_destroy(speech);
+	}
+	ast_websocket_remove_protocol("_aeap_test_speech_", speech_test_server_cb);
+
+	return res;
+}
+
+static struct ast_http_server *http_server;
+
+static int load_module(void)
+{
+	if (!(http_server = ast_http_test_server_get("aeap transport http server", NULL))) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	AST_TEST_REGISTER(res_speech_aeap_test);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	AST_TEST_UNREGISTER(res_speech_aeap_test);
+
+	ast_http_test_server_discard(http_server);
+
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asterisk External Application Protocol Speech test(s)",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.requires = "res_speech_aeap",
+);
diff --git a/tests/test_aeap_transaction.c b/tests/test_aeap_transaction.c
new file mode 100644
index 0000000000000000000000000000000000000000..f1879adb43d6297a9984a4a06d70127bb99b36ba
--- /dev/null
+++ b/tests/test_aeap_transaction.c
@@ -0,0 +1,179 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<depend>res_aeap</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pthread.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/test.h"
+#include "asterisk/module.h"
+#include "asterisk/res_aeap.h"
+#include "asterisk/res_aeap_message.h"
+
+#include "../res/res_aeap/general.h"
+#include "../res/res_aeap/transaction.h"
+
+#define CATEGORY "/res/aeap/transaction/"
+
+#define AEAP_TRANSACTION_ID "foo"
+
+static void handle_timeout(struct ast_aeap *aeap, struct ast_aeap_message *msg, void *obj)
+{
+	int *passed = obj;
+
+	++*passed;
+}
+
+static void *end_transaction(void *data)
+{
+	/* Delay a second before ending transaction */
+	struct timespec delay = { 1, 0 };
+	int *passed = aeap_transaction_user_obj(data);
+
+	while (nanosleep(&delay, &delay));
+
+	++*passed;
+	aeap_transaction_end(data, 0);
+
+	return NULL;
+}
+
+static enum ast_test_result_state exec(struct ast_test *test,
+	struct ast_aeap_tsx_params *params)
+{
+	pthread_t thread_id = AST_PTHREADT_NULL;
+	struct ao2_container *tsxs = NULL;
+	struct aeap_transaction *tsx = NULL;
+	enum ast_test_result_state res = AST_TEST_FAIL;
+	int passed = 0;
+
+	tsxs = aeap_transactions_create();
+	if (!tsxs) {
+		ast_test_status_update(test, "Failed to create transactions object\n");
+		goto exec_cleanup;
+	}
+
+	params->wait = 1;
+	params->obj = &passed;
+
+	tsx = aeap_transaction_create_and_add(tsxs, AEAP_TRANSACTION_ID, params, NULL);
+	if (!tsx) {
+		ast_test_status_update(test, "Failed to create transaction object\n");
+		goto exec_cleanup;
+	}
+
+	if (ast_pthread_create(&thread_id, NULL, end_transaction, ao2_bump(tsx))) {
+		ast_test_status_update(test, "Failed to create response thread\n");
+		ao2_ref(tsx, -1);
+		goto exec_cleanup;
+	}
+
+	if (aeap_transaction_start(tsx)) {
+		ast_test_status_update(test, "Failed to start transaction request\n");
+		goto exec_cleanup;
+	}
+
+	if (passed == 1) {
+		res = AST_TEST_PASS;
+	}
+
+exec_cleanup:
+
+	if (thread_id != AST_PTHREADT_NULL) {
+		pthread_cancel(thread_id);
+		pthread_join(thread_id, NULL);
+	}
+
+	aeap_transaction_end(tsx, 0);
+	ao2_cleanup(tsxs);
+
+	return res;
+}
+
+AST_TEST_DEFINE(transaction_exec)
+{
+	struct ast_aeap_tsx_params params = {
+		.timeout = 5000, /* Give plenty of time for test thread to end */
+	};
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->explicit_only = 0;
+		info->category = CATEGORY;
+		info->summary = "test creating a basic AEAP transaction request";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return exec(test, &params);
+}
+
+AST_TEST_DEFINE(transaction_exec_timeout)
+{
+	struct ast_aeap_tsx_params params = {
+		.timeout = 100, /* Ensure timeout occurs before test thread ends */
+		.on_timeout = handle_timeout,
+	};
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->explicit_only = 0;
+		info->category = CATEGORY;
+		info->summary = "test creating a AEAP transaction request that times out";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return exec(test, &params);
+}
+
+static int load_module(void)
+{
+	AST_TEST_REGISTER(transaction_exec);
+	AST_TEST_REGISTER(transaction_exec_timeout);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	AST_TEST_UNREGISTER(transaction_exec_timeout);
+	AST_TEST_UNREGISTER(transaction_exec);
+
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asterisk External Application Protocol Transaction Tests",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.requires = "res_aeap",
+);
diff --git a/tests/test_aeap_transport.c b/tests/test_aeap_transport.c
new file mode 100644
index 0000000000000000000000000000000000000000..cee9c9e958eee73c504d6ad86bb16ec36a125702
--- /dev/null
+++ b/tests/test_aeap_transport.c
@@ -0,0 +1,249 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2021, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell@sangoma.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<depend>res_aeap</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/http.h"
+#include "asterisk/test.h"
+#include "asterisk/module.h"
+
+#include "../res/res_aeap/transport.h"
+
+#define CATEGORY "/res/aeap/transport/"
+
+#define ADDR "127.0.0.1:8088"
+#define TRANSPORT_URL "ws://" ADDR "/ws"
+#define TRANSPORT_URL_INVALID "ws://" ADDR "/invalid"
+#define TRANSPORT_PROTOCOL "echo"
+#define TRANSPORT_PROTOCOL_INVALID "invalid"
+#define TRANSPORT_TIMEOUT 2000
+
+AST_TEST_DEFINE(transport_create_invalid)
+{
+	RAII_VAR(struct aeap_transport *, transport, NULL, aeap_transport_destroy);
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->explicit_only = 0;
+		info->category = CATEGORY;
+		info->summary = "test creating an AEAP invalid transport type";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	/* Transport is expected to be NULL here */
+	ast_test_validate(test, !(transport = aeap_transport_create("invalid")));
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(transport_create)
+{
+	RAII_VAR(struct aeap_transport *, transport, NULL, aeap_transport_destroy);
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->explicit_only = 0;
+		info->category = CATEGORY;
+		info->summary = "test creating an AEAP transport";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	/* Type is based off the scheme, so just pass in the URL here */
+	ast_test_validate(test, (transport = aeap_transport_create(TRANSPORT_URL)));
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(transport_connect)
+{
+	RAII_VAR(struct aeap_transport *, transport, NULL, aeap_transport_destroy);
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->explicit_only = 0;
+		info->category = CATEGORY;
+		info->summary = "test connecting to an AEAP transport";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	/* Type is based off the scheme, so just pass in the URL for the type */
+	ast_test_validate(test, (transport = aeap_transport_create_and_connect(
+		TRANSPORT_URL, TRANSPORT_URL, TRANSPORT_PROTOCOL, TRANSPORT_TIMEOUT)));
+
+	ast_test_validate(test, aeap_transport_is_connected(transport));
+	ast_test_validate(test, !aeap_transport_disconnect(transport));
+	ast_test_validate(test, !aeap_transport_is_connected(transport));
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(transport_connect_fail)
+{
+	RAII_VAR(struct aeap_transport *, transport, NULL, aeap_transport_destroy);
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->explicit_only = 0;
+		info->category = CATEGORY;
+		info->summary = "test connecting failure for an AEAP transport";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	/* Test invalid address */
+	ast_test_validate(test, (transport = aeap_transport_create(TRANSPORT_URL)));
+
+	ast_test_validate(test, aeap_transport_connect(transport,
+		TRANSPORT_URL_INVALID, TRANSPORT_PROTOCOL, TRANSPORT_TIMEOUT));
+
+	ast_test_validate(test, !aeap_transport_is_connected(transport));
+
+	aeap_transport_destroy(transport);
+
+	/* /\* Test invalid protocol *\/ */
+	ast_test_validate(test, (transport = aeap_transport_create(TRANSPORT_URL)));
+
+	ast_test_validate(test, aeap_transport_connect(transport,
+		TRANSPORT_URL, TRANSPORT_PROTOCOL_INVALID, TRANSPORT_TIMEOUT));
+
+	ast_test_validate(test, !aeap_transport_is_connected(transport));
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(transport_binary)
+{
+	RAII_VAR(struct aeap_transport *, transport, NULL, aeap_transport_destroy);
+	int num = 38;
+	enum AST_AEAP_DATA_TYPE rtype;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->explicit_only = 0;
+		info->category = CATEGORY;
+		info->summary = "test binary I/O from an AEAP transport";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_validate(test, (transport = aeap_transport_create_and_connect(
+		TRANSPORT_URL, TRANSPORT_URL, TRANSPORT_PROTOCOL, TRANSPORT_TIMEOUT)));
+
+	ast_test_validate(test, aeap_transport_write(transport, &num, sizeof(num),
+		AST_AEAP_DATA_TYPE_BINARY) == sizeof(num));
+	ast_test_validate(test, aeap_transport_read(transport, &num,
+		sizeof(num), &rtype) == sizeof(num));
+	ast_test_validate(test, rtype == AST_AEAP_DATA_TYPE_BINARY);
+	ast_test_validate(test, num == 38);
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(transport_string)
+{
+	RAII_VAR(struct aeap_transport *, transport, NULL, aeap_transport_destroy);
+	char buf[16];
+	enum AST_AEAP_DATA_TYPE rtype;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = __func__;
+		info->explicit_only = 0;
+		info->category = CATEGORY;
+		info->summary = "test string I/O from an AEAP transport";
+		info->description = info->summary;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_validate(test, (transport = aeap_transport_create_and_connect(
+		TRANSPORT_URL, TRANSPORT_URL, TRANSPORT_PROTOCOL, TRANSPORT_TIMEOUT)));
+
+	ast_test_validate(test, aeap_transport_write(transport, "foo bar baz", 11,
+		AST_AEAP_DATA_TYPE_STRING) == 11);
+	ast_test_validate(test, aeap_transport_read(transport, buf,
+		sizeof(buf) / sizeof(char), &rtype) == 11);
+	ast_test_validate(test, rtype == AST_AEAP_DATA_TYPE_STRING);
+	ast_test_validate(test, !strcmp(buf, "foo bar baz"));
+
+	return AST_TEST_PASS;
+}
+
+static struct ast_http_server *http_server;
+
+static int load_module(void)
+{
+	if (!(http_server = ast_http_test_server_get("aeap transport http server", NULL))) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	AST_TEST_REGISTER(transport_string);
+	AST_TEST_REGISTER(transport_binary);
+	AST_TEST_REGISTER(transport_connect_fail);
+	AST_TEST_REGISTER(transport_connect);
+	AST_TEST_REGISTER(transport_create);
+	AST_TEST_REGISTER(transport_create_invalid);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	AST_TEST_UNREGISTER(transport_string);
+	AST_TEST_UNREGISTER(transport_binary);
+	AST_TEST_UNREGISTER(transport_connect_fail);
+	AST_TEST_UNREGISTER(transport_connect);
+	AST_TEST_UNREGISTER(transport_create);
+	AST_TEST_UNREGISTER(transport_create_invalid);
+
+	ast_http_test_server_discard(http_server);
+
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asterisk External Application Protocol Transport Tests",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.requires = "res_aeap",
+);
diff --git a/tests/test_capture.c b/tests/test_capture.c
new file mode 100644
index 0000000000000000000000000000000000000000..33809df9ab30f88f142e7c0ccc043a3da5e2e323
--- /dev/null
+++ b/tests/test_capture.c
@@ -0,0 +1,379 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Philip Prindeville
+ *
+ * Philip Prindeville <philipp@redfish-solutions.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Make basic use of capture capability in test framework.
+ *
+ * \author\verbatim Philip Prindeville <philipp@redfish-solutions.com> \endverbatim
+ *
+ * Exercise the capture capabilities built into the test framework so
+ * that external commands might be used to generate validating results
+ * used on corroborating tests.
+ * \ingroup tests
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+
+AST_TEST_DEFINE(test_capture_true)
+{
+	int status = AST_TEST_FAIL;
+	struct ast_test_capture cap;
+	const char *command = "true";
+	char *const args[] = { "true", NULL };
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "test_capture_true";
+		info->category = "/main/test_capture/";
+		info->summary = "capture true exit unit test";
+		info->description =
+			"Capture exit code from true command.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_status_update(test, "Executing true exit test...\n");
+
+	if (!ast_check_command_in_path(command)) {
+		ast_test_status_update(test, "couldn't find %s\n", command);
+		return status;
+	}
+
+	if (ast_test_capture_command(&cap, command, args, NULL, 0) != 1) {
+		ast_test_status_update(test, "ast_test_capture_command() failed\n");
+		return status;
+	}
+
+	if (cap.outlen != 0) {
+		ast_test_status_update(test, "unexpected value for stdout\n");
+		goto cleanup;
+	}
+
+	if (cap.errlen != 0) {
+		ast_test_status_update(test, "unexpected value for stderr\n");
+		goto cleanup;
+	}
+
+	if (cap.pid == -1) {
+		ast_test_status_update(test, "invalid process id\n");
+		goto cleanup;
+	}
+
+	if (cap.exitcode != 0) {
+		ast_test_status_update(test, "child exited %d\n", cap.exitcode);
+		goto cleanup;
+	}
+
+	status = AST_TEST_PASS;
+
+cleanup:
+	ast_test_capture_free(&cap);
+
+	return status;
+}
+
+AST_TEST_DEFINE(test_capture_false)
+{
+	int status = AST_TEST_FAIL;
+	struct ast_test_capture cap;
+	const char *command = "false";
+	char *const args[] = { "false", NULL };
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "test_capture_false";
+		info->category = "/main/test_capture/";
+		info->summary = "capture false exit unit test";
+		info->description =
+			"Capture exit code from false command.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_status_update(test, "Executing false exit test...\n");
+
+	if (!ast_check_command_in_path(command)) {
+		ast_test_status_update(test, "couldn't find %s\n", command);
+		return status;
+	}
+
+	if (ast_test_capture_command(&cap, command, args, NULL, 0) != 1) {
+		ast_test_status_update(test, "ast_test_capture_command() failed\n");
+		return status;
+	}
+
+	if (cap.outlen != 0) {
+		ast_test_status_update(test, "unexpected value for stdout\n");
+		goto cleanup;
+	}
+
+	if (cap.errlen != 0) {
+		ast_test_status_update(test, "unexpected value for stderr\n");
+		goto cleanup;
+	}
+
+	if (cap.pid == -1) {
+		ast_test_status_update(test, "invalid process id\n");
+		goto cleanup;
+	}
+
+	if (cap.exitcode != 1) {
+		ast_test_status_update(test, "child exited %d\n", cap.exitcode);
+		goto cleanup;
+	}
+
+	status = AST_TEST_PASS;
+
+cleanup:
+	ast_test_capture_free(&cap);
+
+	return status;
+}
+
+AST_TEST_DEFINE(test_capture_with_stdin)
+{
+	int status = AST_TEST_FAIL;
+	struct ast_test_capture cap;
+	const char *command = "base64";
+	char *const args[] = { "base64", NULL };
+	const char data[] = "Mary had a little lamb.";
+	const unsigned datalen = sizeof(data) - 1;
+	const char output[] = "TWFyeSBoYWQgYSBsaXR0bGUgbGFtYi4=\n";
+	const unsigned outputlen = sizeof(output) - 1;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "test_capture_with_stdin";
+		info->category = "/main/test_capture/";
+		info->summary = "capture with stdin unit test";
+		info->description =
+			"Capture output from stdin transformation command.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_status_update(test, "Executing stdin test...\n");
+
+	if (!ast_check_command_in_path(command)) {
+		ast_test_status_update(test, "couldn't find %s\n", command);
+		return status;
+	}
+
+	if (ast_test_capture_command(&cap, command, args, data, datalen) != 1) {
+		ast_test_status_update(test, "ast_test_capture_command() failed\n");
+		return status;
+	}
+
+	if (cap.outlen != outputlen || memcmp(cap.outbuf, output, cap.outlen)) {
+		ast_test_status_update(test, "unexpected value for stdout\n");
+		goto cleanup;
+	}
+
+	if (cap.errlen != 0) {
+		ast_test_status_update(test, "unexpected value for stderr\n");
+		goto cleanup;
+	}
+
+	if (cap.pid == -1) {
+		ast_test_status_update(test, "invalid process id\n");
+		goto cleanup;
+	}
+
+	if (cap.exitcode != 0) {
+		ast_test_status_update(test, "child exited %d\n", cap.exitcode);
+		goto cleanup;
+	}
+
+	status = AST_TEST_PASS;
+
+cleanup:
+	ast_test_capture_free(&cap);
+
+	return status;
+}
+
+AST_TEST_DEFINE(test_capture_with_dynamic)
+{
+	int status = AST_TEST_FAIL;
+	struct ast_test_capture cap;
+	const char *command = "date";
+	char *args[] = { "date", "DATE", "FORMAT", NULL };
+	char date[40];
+	const char format[] = "+%a, %d %b %y %T %z";
+	const char format2[] = "%a, %d %b %y %T %z\n";
+	char myresult[64];
+	unsigned myresultlen;
+	time_t now;
+	struct tm *tm;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "test_capture_with_dynamic";
+		info->category = "/main/test_capture/";
+		info->summary = "capture with dynamic argument unit test";
+		info->description =
+			"Capture output from dynamic transformation command.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_status_update(test, "Executing dynamic argument test...\n");
+
+	if (!ast_check_command_in_path(command)) {
+		ast_test_status_update(test, "couldn't find %s\n", command);
+		return status;
+	}
+
+	time(&now);
+	snprintf(date, sizeof(date), "--date=@%lu", now);
+
+	tm = localtime(&now);
+	strftime(myresult, sizeof(myresult), format2, tm);
+	myresultlen = strlen(myresult);
+
+	args[1] = date;
+	args[2] = (char *)format;
+
+	if (ast_test_capture_command(&cap, command, args, NULL, 0) != 1) {
+		ast_test_status_update(test, "ast_test_capture_command() failed\n");
+		return status;
+	}
+
+	if (cap.outlen != myresultlen || memcmp(cap.outbuf, myresult, cap.outlen)) {
+		ast_test_status_update(test, "unexpected value for stdout\n");
+		goto cleanup;
+	}
+
+	if (cap.errlen != 0) {
+		ast_test_status_update(test, "unexpected value for stderr\n");
+		goto cleanup;
+	}
+
+	if (cap.pid == -1) {
+		ast_test_status_update(test, "invalid process id\n");
+		goto cleanup;
+	}
+
+	if (cap.exitcode != 0) {
+		ast_test_status_update(test, "child exited %d\n", cap.exitcode);
+		goto cleanup;
+	}
+
+	status = AST_TEST_PASS;
+
+cleanup:
+	ast_test_capture_free(&cap);
+
+	return status;
+}
+
+AST_TEST_DEFINE(test_capture_stdout_stderr)
+{
+	int status = AST_TEST_FAIL;
+	struct ast_test_capture cap;
+	const char *command = "sh";
+	char *const args[] = { "sh", "-c", "echo -n 'foo' >&2 ; echo -n 'zzz' >&1 ; echo -n 'bar' >&2", NULL };
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "test_capture_stdout_stderr";
+		info->category = "/main/test_capture/";
+		info->summary = "capture stdout & stderr unit test";
+		info->description =
+			"Capture both stdout and stderr from shell.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_status_update(test, "Executing stdout/stderr test...\n");
+
+	if (!ast_check_command_in_path(command)) {
+		ast_test_status_update(test, "couldn't find %s\n", command);
+		return status;
+	}
+
+	if (ast_test_capture_command(&cap, command, args, NULL, 0) != 1) {
+		ast_test_status_update(test, "ast_test_capture_command() failed\n");
+		return status;
+	}
+
+	if (cap.outlen != 3 || memcmp(cap.outbuf, "zzz", 3)) {
+		ast_test_status_update(test, "unexpected value for stdout\n");
+		goto cleanup;
+	}
+
+	if (cap.errlen != 6 || memcmp(cap.errbuf, "foobar", 6)) {
+		ast_test_status_update(test, "unexpected value for stderr\n");
+		goto cleanup;
+	}
+
+	if (cap.pid == -1) {
+		ast_test_status_update(test, "invalid process id\n");
+		goto cleanup;
+	}
+
+	if (cap.exitcode != 0) {
+		ast_test_status_update(test, "child exited %d\n", cap.exitcode);
+		goto cleanup;
+	}
+
+	status = AST_TEST_PASS;
+
+cleanup:
+	ast_test_capture_free(&cap);
+
+	return status;
+}
+
+static int unload_module(void)
+{
+	AST_TEST_UNREGISTER(test_capture_with_stdin);
+	AST_TEST_UNREGISTER(test_capture_with_dynamic);
+	AST_TEST_UNREGISTER(test_capture_stdout_stderr);
+	AST_TEST_UNREGISTER(test_capture_true);
+	AST_TEST_UNREGISTER(test_capture_false);
+	return 0;
+}
+
+static int load_module(void)
+{
+	AST_TEST_REGISTER(test_capture_with_stdin);
+	AST_TEST_REGISTER(test_capture_with_dynamic);
+	AST_TEST_REGISTER(test_capture_stdout_stderr);
+	AST_TEST_REGISTER(test_capture_true);
+	AST_TEST_REGISTER(test_capture_false);
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Capture support test");
+
diff --git a/tests/test_config.c b/tests/test_config.c
index 1a0ddaf836cd82e22d583c515adb46a8d4817bcf..1d44e5d5baf464a2f5d2d0035dc0381bcd880b28 100644
--- a/tests/test_config.c
+++ b/tests/test_config.c
@@ -1952,7 +1952,7 @@ AST_TEST_DEFINE(variable_list_from_string)
 
 	switch (cmd) {
 	case TEST_INIT:
-		info->name = "variable_list_from_string";
+		info->name = "variable_list_from_quoted_string";
 		info->category = "/main/config/";
 		info->summary = "Test parsing a string into a variable list";
 		info->description =	info->summary;
@@ -1961,13 +1961,13 @@ AST_TEST_DEFINE(variable_list_from_string)
 		break;
 	}
 
-	parse_string = "abc = 'def', ghi = 'j,kl', mno='pq=r', stu = 'vwx=\"yz\", ABC = \"DEF\"'";
-	list = ast_variable_list_from_string(parse_string, ",", "=");
+	parse_string = "000= '', 111=, 222 = , 333 = ' ', abc = 'def', ghi = 'j,kl', mno='pq=r', stu = 'vwx=\"yz\", ABC = \"DEF\"'";
+	list = ast_variable_list_from_quoted_string(parse_string, ",", "=", "'");
 	ast_test_validate(test, list != NULL);
 	str = ast_variable_list_join(list, "|", "^", "@", NULL);
 
 	ast_test_validate(test,
-		strcmp(ast_str_buffer(str), "abc^@def@|ghi^@j,kl@|mno^@pq=r@|stu^@vwx=\"yz\", ABC = \"DEF\"@") == 0);
+		strcmp(ast_str_buffer(str), "000^@@|111^@@|222^@@|333^@ @|abc^@def@|ghi^@j,kl@|mno^@pq=r@|stu^@vwx=\"yz\", ABC = \"DEF\"@") == 0);
 
 	return AST_TEST_PASS;
 }
diff --git a/tests/test_conversions.c b/tests/test_conversions.c
index f49c8e68008b04e546ef35ae3dda587c63eabb39..7ec1731c94e3c4859dba852da78c8392d1d25444 100644
--- a/tests/test_conversions.c
+++ b/tests/test_conversions.c
@@ -48,6 +48,7 @@ AST_TEST_DEFINE(str_to_int)
 	const char *spaces = "  ";
 	const char *valid = "7";
 	const char *valid_spaces = "  7";
+	const char *valid_decimal = "08";
 	int val;
 	char str[64];
 
@@ -73,6 +74,7 @@ AST_TEST_DEFINE(str_to_int)
 	ast_test_validate(test, ast_str_to_int(spaces, &val));
 	ast_test_validate(test, !ast_str_to_int(valid, &val));
 	ast_test_validate(test, !ast_str_to_int(valid_spaces, &val));
+	ast_test_validate(test, !ast_str_to_int(valid_decimal, &val));
 
 	ast_test_validate(test, snprintf(str, sizeof(str), "%d", INT_MAX) > 0);
 	ast_test_validate(test, !ast_str_to_int(str, &val));
@@ -95,6 +97,7 @@ AST_TEST_DEFINE(str_to_uint)
 	const char *spaces = "  ";
 	const char *valid = "7";
 	const char *valid_spaces = "  7";
+	const char *valid_decimal = "08";
 	unsigned int val;
 	char str[64];
 
@@ -119,6 +122,7 @@ AST_TEST_DEFINE(str_to_uint)
 	ast_test_validate(test, ast_str_to_uint(spaces, &val));
 	ast_test_validate(test, !ast_str_to_uint(valid, &val));
 	ast_test_validate(test, !ast_str_to_uint(valid_spaces, &val));
+	ast_test_validate(test, !ast_str_to_uint(valid_decimal, &val));
 
 	ast_test_validate(test, snprintf(str, sizeof(str), "%u", UINT_MAX) > 0);
 	ast_test_validate(test, !ast_str_to_uint(str, &val));
@@ -138,6 +142,7 @@ AST_TEST_DEFINE(str_to_long)
 	const char *spaces = "  ";
 	const char *valid = "7";
 	const char *valid_spaces = "  7";
+	const char *valid_decimal = "08";
 	long val;
 	char str[64];
 
@@ -163,6 +168,7 @@ AST_TEST_DEFINE(str_to_long)
 	ast_test_validate(test, ast_str_to_long(spaces, &val));
 	ast_test_validate(test, !ast_str_to_long(valid, &val));
 	ast_test_validate(test, !ast_str_to_long(valid_spaces, &val));
+	ast_test_validate(test, !ast_str_to_long(valid_decimal, &val));
 
 	ast_test_validate(test, snprintf(str, sizeof(str), "%ld", LONG_MAX) > 0);
 	ast_test_validate(test, !ast_str_to_long(str, &val));
@@ -185,6 +191,7 @@ AST_TEST_DEFINE(str_to_ulong)
 	const char *spaces = "  ";
 	const char *valid = "7";
 	const char *valid_spaces = "  7";
+	const char *valid_decimal = "08";
 	unsigned long val;
 	char str[64];
 
@@ -209,6 +216,7 @@ AST_TEST_DEFINE(str_to_ulong)
 	ast_test_validate(test, ast_str_to_ulong(spaces, &val));
 	ast_test_validate(test, !ast_str_to_ulong(valid, &val));
 	ast_test_validate(test, !ast_str_to_ulong(valid_spaces, &val));
+	ast_test_validate(test, !ast_str_to_ulong(valid_decimal, &val));
 
 	ast_test_validate(test, snprintf(str, sizeof(str), "%lu", ULONG_MAX) > 0);
 	ast_test_validate(test, !ast_str_to_ulong(str, &val));
@@ -228,6 +236,7 @@ AST_TEST_DEFINE(str_to_imax)
 	const char *spaces = "  ";
 	const char *valid = "7";
 	const char *valid_spaces = "  7";
+	const char *valid_decimal = "08";
 	intmax_t val;
 	char str[64];
 
@@ -253,6 +262,7 @@ AST_TEST_DEFINE(str_to_imax)
 	ast_test_validate(test, ast_str_to_imax(spaces, &val));
 	ast_test_validate(test, !ast_str_to_imax(valid, &val));
 	ast_test_validate(test, !ast_str_to_imax(valid_spaces, &val));
+	ast_test_validate(test, !ast_str_to_imax(valid_decimal, &val));
 
 	ast_test_validate(test, snprintf(str, sizeof(str), "%jd", INTMAX_MAX) > 0);
 	ast_test_validate(test, !ast_str_to_imax(str, &val));
@@ -276,6 +286,7 @@ AST_TEST_DEFINE(str_to_umax)
 	const char *spaces = "  ";
 	const char *valid = "7";
 	const char *valid_spaces = "  7";
+	const char *valid_decimal = "08";
 	uintmax_t val;
 	char str[64];
 
@@ -300,6 +311,7 @@ AST_TEST_DEFINE(str_to_umax)
 	ast_test_validate(test, ast_str_to_umax(spaces, &val));
 	ast_test_validate(test, !ast_str_to_umax(valid, &val));
 	ast_test_validate(test, !ast_str_to_umax(valid_spaces, &val));
+	ast_test_validate(test, !ast_str_to_umax(valid_decimal, &val));
 
 	ast_test_validate(test, snprintf(str, sizeof(str), "%ju", UINTMAX_MAX) > 0);
 	ast_test_validate(test, !ast_str_to_umax(str, &val));
diff --git a/tests/test_crypto.c b/tests/test_crypto.c
new file mode 100644
index 0000000000000000000000000000000000000000..8b52c9df383e30b444e68719bf7c06d459efe46d
--- /dev/null
+++ b/tests/test_crypto.c
@@ -0,0 +1,689 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2022, Philip Prindeville
+ *
+ * Philip Prindeville <philipp@redfish-solutions.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Unit Tests for crypto API
+ *
+ * \author Philip Prindeville <philipp@redfish-solutions.com>
+ */
+
+/*** MODULEINFO
+        <depend>TEST_FRAMEWORK</depend>
+        <depend>res_crypto</depend>
+        <depend>crypto</depend>
+        <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/utils.h"
+#include "asterisk/test.h"
+#include "asterisk/crypto.h"
+#include "asterisk/paths.h"
+#include "asterisk/module.h"
+#include "asterisk/file.h"
+
+#include <assert.h>
+#include <sys/stat.h>
+#include <linux/limits.h>
+#include <openssl/evp.h>
+
+static const char *keypair1 = "rsa_key1";
+
+static const char *old_key_dir = NULL;
+
+static char *hexstring(const unsigned char *data, unsigned datalen)
+{
+	char *buf = ast_malloc(datalen * 2 + 1);
+	unsigned n;
+
+	for (n = 0; n < datalen; ++n) {
+		snprintf(&buf[n * 2], 3, "%02x", data[n]);
+	}
+	buf[datalen * 2] = '\0';
+
+	return buf;
+}
+
+static void push_key_dir(const char *dir)
+{
+	assert(old_key_dir == NULL);
+
+	old_key_dir = ast_config_AST_KEY_DIR;
+
+	ast_config_AST_KEY_DIR = ast_strdup(dir);
+}
+
+static void pop_key_dir(void)
+{
+	assert(old_key_dir != NULL);
+
+	ast_free((char *)ast_config_AST_KEY_DIR);
+
+	ast_config_AST_KEY_DIR = old_key_dir;
+
+	old_key_dir = NULL;
+}
+
+AST_TEST_DEFINE(crypto_rsa_encrypt)
+{
+	int res = AST_TEST_FAIL;
+	struct ast_key *key = NULL;
+	const unsigned char plaintext[23] = "Mary had a little lamb.";
+	char wd[PATH_MAX], key_dir[PATH_MAX], priv[PATH_MAX];
+	unsigned char buf[AST_CRYPTO_RSA_KEY_BITS / 8];
+	const char *command = "openssl";
+	char *args[] = { "openssl", "pkeyutl", "-decrypt", "-inkey", "PRIVATE", "-pkeyopt", "rsa_padding_mode:oaep", NULL };
+	enum { PRIVATE = 4 };
+	struct ast_test_capture cap;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "crypto_rsa_encrypt";
+		info->category = "/res/res_crypto/";
+		info->summary = "Encrypt w/ RSA public key";
+		info->description = "Encrypt string with RSA public key";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_status_update(test, "Executing RSA encryption test\n");
+
+	ast_test_capture_init(&cap);
+
+	if (!ast_check_command_in_path(command)) {
+		ast_test_status_update(test, "couldn't find %s\n", command);
+		ast_test_capture_free(&cap);
+		return res;
+	}
+
+	if (getcwd(wd, sizeof(wd)) == NULL) {
+		ast_test_status_update(test, "Could not determine current working directory\n");
+		ast_test_capture_free(&cap);
+		return res;
+	}
+
+	snprintf(key_dir, sizeof(key_dir), "%s/%s", wd, "tests/keys");
+	push_key_dir((const char *)key_dir);
+	snprintf(priv, sizeof(priv), "%s/%s.key", key_dir, keypair1);
+
+	/* because git doesn't preserve permissions */
+	(void)chmod(priv, 0400);
+
+	if (ast_crypto_reload() != 1) {
+		ast_test_status_update(test, "Couldn't force crypto reload\n");
+		goto cleanup;
+	}
+
+	key = ast_key_get(keypair1, AST_KEY_PUBLIC);
+
+	if (!key) {
+		ast_test_status_update(test, "Couldn't read key: %s\n", keypair1);
+		goto cleanup;
+	}
+
+	memset(buf, 0, sizeof(buf));
+	ast_encrypt_bin(buf, plaintext, sizeof(plaintext), key);
+
+	args[PRIVATE] = priv;
+	if (ast_test_capture_command(&cap, command, args, (const char *)buf, sizeof(buf)) != 1) {
+		ast_test_status_update(test, "ast_test_capture_command() failed\n");
+		goto cleanup;
+	}
+
+	if (cap.outlen != sizeof(plaintext) || memcmp(cap.outbuf, plaintext, cap.outlen)) {
+		ast_test_status_update(test, "Unexpected value/length for stdout: '%.*s' (%zu)\n", (int) cap.outlen, cap.outbuf, cap.outlen);
+		goto cleanup;
+	}
+
+	if (cap.errlen != 0) {
+		ast_test_status_update(test, "Unexpected length for stderr: '%.*s' (%zu)\n", (int) cap.errlen, cap.errbuf, cap.errlen);
+		goto cleanup;
+	}
+
+	if (cap.pid == -1) {
+		ast_test_status_update(test, "Invalid process id\n");
+		goto cleanup;
+	}
+
+	if (cap.exitcode != 0) {
+		ast_test_status_update(test, "Child exited %d\n", cap.exitcode);
+		goto cleanup;
+	}
+
+	res = AST_TEST_PASS;
+
+cleanup:
+	ast_test_capture_free(&cap);
+	pop_key_dir();
+	return res;
+}
+
+AST_TEST_DEFINE(crypto_rsa_decrypt)
+{
+	int res = AST_TEST_FAIL;
+	struct ast_key *key = NULL;
+	const unsigned char plaintext[23] = "Mary had a little lamb.";
+	char wd[PATH_MAX], key_dir[PATH_MAX], pub[PATH_MAX];
+	unsigned char buf[AST_CRYPTO_RSA_KEY_BITS / 8];
+	const char *command = "openssl";
+	char *args[] = { "openssl", "pkeyutl", "-encrypt", "-pubin", "-inkey", "PUBLIC", "-pkeyopt", "rsa_padding_mode:oaep", NULL };
+	enum { PUBLIC = 5 };
+	struct ast_test_capture cap;
+	int len;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "crypto_decrypt_pub_key";
+		info->category = "/res/res_crypto/";
+		info->summary = "Decrypt w/ RSA public key";
+		info->description = "Decrypt string with RSA private key";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_status_update(test, "Executing RSA decryption test\n");
+
+	ast_test_capture_init(&cap);
+
+	if (!ast_check_command_in_path(command)) {
+		ast_test_status_update(test, "couldn't find %s\n", command);
+		ast_test_capture_free(&cap);
+		return res;
+	}
+
+	if (getcwd(wd, sizeof(wd)) == NULL) {
+		ast_test_status_update(test, "Could not determine current working directory\n");
+		ast_test_capture_free(&cap);
+		return res;
+	}
+
+	snprintf(key_dir, sizeof(key_dir), "%s/%s", wd, "tests/keys");
+	push_key_dir((const char *)key_dir);
+	snprintf(pub, sizeof(pub), "%s/%s.pub", key_dir, keypair1);
+
+	if (ast_crypto_reload() != 1) {
+		ast_test_status_update(test, "Couldn't force crypto reload\n");
+		goto cleanup;
+	}
+
+	key = ast_key_get(keypair1, AST_KEY_PRIVATE);
+
+	if (!key) {
+		ast_test_status_update(test, "Couldn't read key: %s\n", keypair1);
+		goto cleanup;
+	}
+
+	args[PUBLIC] = pub;
+	if (ast_test_capture_command(&cap, command, args, (const char *)plaintext, sizeof(plaintext)) != 1) {
+		ast_test_status_update(test, "ast_test_capture_command() failed\n");
+		goto cleanup;
+	}
+
+	if (cap.outlen != sizeof(buf)) {
+		ast_test_status_update(test, "Unexpected length for stdout: %zu\n", cap.outlen);
+		goto cleanup;
+	}
+
+	if (cap.errlen != 0) {
+		ast_test_status_update(test, "Unexpected value/length for stderr: '%.*s' (%zu)\n", (int) cap.errlen, cap.errbuf, cap.errlen);
+		goto cleanup;
+	}
+
+	if (cap.pid == -1) {
+		ast_test_status_update(test, "Invalid process id\n");
+		goto cleanup;
+	}
+
+	if (cap.exitcode != 0) {
+		ast_test_status_update(test, "Child exited %d\n", cap.exitcode);
+		goto cleanup;
+	}
+
+	memset(buf, 0, sizeof(buf));
+	len = ast_decrypt_bin(buf, (unsigned char *)cap.outbuf, cap.outlen, key);
+
+	if (len != sizeof(plaintext) || memcmp(buf, plaintext, len)) {
+		ast_test_status_update(test, "Unexpected value for decrypted text\n");
+		goto cleanup;
+	}
+
+	res = AST_TEST_PASS;
+
+cleanup:
+	ast_test_capture_free(&cap);
+	pop_key_dir();
+	return res;
+}
+
+AST_TEST_DEFINE(crypto_sign)
+{
+	int res = AST_TEST_FAIL;
+	struct ast_key *key = NULL;
+	const char plaintext[23] = "Mary had a little lamb.";
+	char wd[PATH_MAX], key_dir[PATH_MAX], pub[PATH_MAX];
+	unsigned char buf[AST_CRYPTO_RSA_KEY_BITS / 8];
+	const char *command = "openssl";
+	char *args[] = { "openssl", "pkeyutl", "-verify", "-inkey", "PUBLIC", "-pubin", "-sigfile", "SIGNATURE", "-pkeyopt", "digest:sha1", NULL };
+	enum { PUBLIC = 4, SIGNATURE = 7 };
+	struct ast_test_capture cap;
+	unsigned char digest[20];
+	unsigned digestlen;
+	EVP_MD_CTX *ctx;
+	FILE *fsig = NULL;
+	char signpath[64] = "/tmp/signingXXXXXX";
+	const char success[] = "Signature Verified Successfully\n";
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "crypto_sign";
+		info->category = "/res/res_crypto/";
+		info->summary = "Sign w/ RSA private key";
+		info->description = "Sign string with RSA private key";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_status_update(test, "Executing RSA signing test\n");
+
+	ast_test_capture_init(&cap);
+
+	if (!ast_check_command_in_path(command)) {
+		ast_test_status_update(test, "couldn't find %s\n", command);
+		ast_test_capture_free(&cap);
+		return res;
+	}
+
+	if (getcwd(wd, sizeof(wd)) == NULL) {
+		ast_test_status_update(test, "Could not determine current working directory\n");
+		ast_test_capture_free(&cap);
+		return res;
+	}
+
+	snprintf(key_dir, sizeof(key_dir), "%s/%s", wd, "tests/keys");
+	push_key_dir((const char *)key_dir);
+	snprintf(pub, sizeof(pub), "%s/%s.pub", key_dir, keypair1);
+
+	ctx = EVP_MD_CTX_create();
+	EVP_DigestInit(ctx, EVP_sha1());
+	EVP_DigestUpdate(ctx, plaintext, sizeof(plaintext));
+	EVP_DigestFinal(ctx, digest, &digestlen);
+	EVP_MD_CTX_destroy(ctx);
+	ctx = NULL;
+
+	if (ast_crypto_reload() != 1) {
+		ast_test_status_update(test, "Couldn't force crypto reload\n");
+		goto cleanup;
+	}
+
+	key = ast_key_get(keypair1, AST_KEY_PRIVATE);
+
+	if (!key) {
+		ast_test_status_update(test, "Couldn't read key: %s\n", keypair1);
+		goto cleanup;
+	}
+
+	memset(buf, 0, sizeof(buf));
+	if (ast_sign_bin(key, plaintext, sizeof(plaintext), buf) != 0) {
+		ast_test_status_update(test, "ast_sign_bin() failed\n");
+		goto cleanup;
+	}
+
+	fsig = ast_file_mkftemp(signpath, 0600);
+	if (fsig == NULL) {
+		ast_test_status_update(test, "Couldn't open temp signing file\n");
+		goto cleanup;
+	}
+	fwrite(buf, sizeof(char), sizeof(buf), fsig);
+	fclose(fsig);
+	fsig = NULL;
+
+	args[PUBLIC] = pub;
+	args[SIGNATURE] = signpath;
+	if (ast_test_capture_command(&cap, command, args, (const char *)digest, digestlen) != 1) {
+		ast_test_status_update(test, "ast_test_capture_command() failed\n");
+		goto cleanup;
+	}
+
+	if (cap.outlen != sizeof(success) - 1 || memcmp(cap.outbuf, success, cap.outlen)) {
+		ast_test_status_update(test, "Unexpected value/length for stdout: '%.*s' (%zu)\n", (int) cap.outlen, cap.outbuf, cap.outlen);
+		goto cleanup;
+	}
+
+	if (cap.errlen != 0) {
+		ast_test_status_update(test, "Unexpected value for stderr: '%.*s' (%zu)\n", (int) cap.errlen, cap.errbuf, cap.errlen);
+		goto cleanup;
+	}
+
+	if (cap.pid == -1) {
+		ast_test_status_update(test, "Invalid process id\n");
+		goto cleanup;
+	}
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+	if (cap.exitcode != 0) {
+#else
+	if (cap.exitcode != 0 && cap.exitcode != 1) {
+#endif
+		ast_test_status_update(test, "Child exited %d\n", cap.exitcode);
+		goto cleanup;
+	}
+
+	res = AST_TEST_PASS;
+
+cleanup:
+	ast_test_capture_free(&cap);
+	unlink(signpath);
+	pop_key_dir();
+	return res;
+}
+
+AST_TEST_DEFINE(crypto_verify)
+{
+	int res = AST_TEST_FAIL;
+	struct ast_key *key = NULL;
+	const char plaintext[23] = "Mary had a little lamb.";
+	char wd[PATH_MAX], key_dir[PATH_MAX], priv[PATH_MAX];
+	const char *command = "openssl";
+	char *args[] = { "openssl", "pkeyutl", "-sign", "-inkey", "PRIVATE", "-pkeyopt", "digest:sha1", NULL };
+	enum { PRIVATE = 4 };
+	struct ast_test_capture cap;
+	unsigned char digest[20];
+	unsigned digestlen;
+	EVP_MD_CTX *ctx;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "crypto_verify";
+		info->category = "/res/res_crypto/";
+		info->summary = "Verify w/ RSA public key";
+		info->description = "Verify signature with RSA public key";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_status_update(test, "Executing RSA signature verification test\n");
+
+	ast_test_capture_init(&cap);
+
+	if (!ast_check_command_in_path(command)) {
+		ast_test_status_update(test, "couldn't find %s\n", command);
+		ast_test_capture_free(&cap);
+		return res;
+	}
+
+	if (getcwd(wd, sizeof(wd)) == NULL) {
+		ast_test_status_update(test, "Could not determine current working directory\n");
+		ast_test_capture_free(&cap);
+		return res;
+	}
+
+	snprintf(key_dir, sizeof(key_dir), "%s/%s", wd, "tests/keys");
+	push_key_dir((const char *)key_dir);
+	snprintf(priv, sizeof(priv), "%s/%s.key", key_dir, keypair1);
+
+	/* because git doesn't preserve permissions */
+	(void)chmod(priv, 0400);
+
+	if (ast_crypto_reload() != 1) {
+		ast_test_status_update(test, "Couldn't force crypto reload\n");
+		goto cleanup;
+	}
+
+	key = ast_key_get(keypair1, AST_KEY_PUBLIC);
+
+	if (!key) {
+		ast_test_status_update(test, "Couldn't read key: %s\n", keypair1);
+		goto cleanup;
+	}
+
+	ctx = EVP_MD_CTX_create();
+	EVP_DigestInit(ctx, EVP_sha1());
+	EVP_DigestUpdate(ctx, plaintext, sizeof(plaintext));
+	EVP_DigestFinal(ctx, digest, &digestlen);
+	EVP_MD_CTX_destroy(ctx);
+
+	args[PRIVATE] = priv;
+	if (ast_test_capture_command(&cap, command, args, (const char *)digest, sizeof(digest)) != 1) {
+		ast_test_status_update(test, "ast_test_capture_command() failed\n");
+		goto cleanup;
+	}
+
+	if (cap.outlen != (AST_CRYPTO_RSA_KEY_BITS / 8)) {
+		ast_test_status_update(test, "Unexpected length for stdout: %zu\n", cap.outlen);
+		goto cleanup;
+	}
+
+	if (cap.errlen != 0) {
+		ast_test_status_update(test, "Unexpected value/length for stderr: '%.*s'\n", (int) cap.errlen, cap.errbuf);
+		goto cleanup;
+	}
+
+	if (cap.pid == -1) {
+		ast_test_status_update(test, "Invalid process id\n");
+		goto cleanup;
+	}
+
+	if (cap.exitcode != 0) {
+		ast_test_status_update(test, "Child exited %d\n", cap.exitcode);
+		goto cleanup;
+	}
+
+	if (ast_check_signature_bin(key, plaintext, sizeof(plaintext), (const unsigned char *)cap.outbuf) != 0) {
+		ast_test_status_update(test, "ast_check_signature_bin() failed\n");
+		goto cleanup;
+	}
+
+	res = AST_TEST_PASS;
+
+cleanup:
+	ast_test_capture_free(&cap);
+	pop_key_dir();
+	return res;
+}
+
+AST_TEST_DEFINE(crypto_aes_encrypt)
+{
+	int res = AST_TEST_FAIL;
+	const unsigned char key[16] = {
+		0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
+		0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01
+	};
+	const unsigned char plaintext[16] = "Mary had a littl";
+	const char *command = "openssl";
+	char *args[] = { "openssl", "enc", "-aes-128-ecb", "-d", "-K", "KEY", "-nopad", NULL };
+	enum { KEY = 5 };
+	struct ast_test_capture cap;
+	unsigned char buf[16];
+	ast_aes_encrypt_key aes_key;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "crypto_aes_encrypt";
+		info->category = "/res/res_crypto/";
+		info->summary = "Encrypt test AES-128-ECB";
+		info->description = "Encrypt a test string using AES-128 and ECB";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_status_update(test, "Executing AES-ECB encryption test\n");
+
+	ast_test_capture_init(&cap);
+
+	if (!ast_check_command_in_path(command)) {
+		ast_test_status_update(test, "couldn't find %s\n", command);
+		return res;
+	}
+
+	memset(buf, 0, sizeof(buf));
+	ast_aes_set_encrypt_key(key, &aes_key);
+	if (ast_aes_encrypt(plaintext, buf, &aes_key) <= 0) {
+		ast_test_status_update(test, "ast_aes_encrypt() failed\n");
+		goto cleanup;
+	}
+
+	args[KEY] = hexstring(key, sizeof(key));
+	if (ast_test_capture_command(&cap, command, args, (const char *)buf, sizeof(buf)) != 1) {
+		ast_test_status_update(test, "ast_test_capture_command() failed\n");
+		goto cleanup;
+	}
+
+	if (cap.outlen != sizeof(plaintext) || memcmp(cap.outbuf, plaintext, cap.outlen)) {
+		ast_test_status_update(test, "Unexpected value/length for stdout: '%.*s' (%zu)\n", (int) cap.outlen, cap.outbuf, cap.outlen);
+		goto cleanup;
+	}
+
+	if (cap.errlen != 0) {
+		ast_test_status_update(test, "Unexpected value/length for stderr: '%.*s'\n", (int) cap.errlen, cap.errbuf);
+		goto cleanup;
+	}
+
+	if (cap.pid == -1) {
+		ast_test_status_update(test, "Invalid process id\n");
+		goto cleanup;
+	}
+
+	if (cap.exitcode != 0) {
+		ast_test_status_update(test, "Child exited %d\n", cap.exitcode);
+		goto cleanup;
+	}
+
+	res = AST_TEST_PASS;
+
+cleanup:
+	ast_free(args[KEY]);
+	ast_test_capture_free(&cap);
+	return res;
+}
+
+AST_TEST_DEFINE(crypto_aes_decrypt)
+{
+	int res = AST_TEST_FAIL;
+	const unsigned char key[16] = {
+		0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
+		0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01
+	};
+	const unsigned char plaintext[16] = "Mary had a littl";
+	unsigned char buf[16];
+	const char *command = "openssl";
+	char *args[] = { "openssl", "enc", "-aes-128-ecb", "-e", "-K", "KEY", "-nopad", NULL };
+	enum { KEY = 5 };
+	struct ast_test_capture cap;
+	ast_aes_encrypt_key aes_key;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "crypto_aes_decrypt";
+		info->category = "/res/res_crypto/";
+		info->summary = "Decrypt test AES-128-ECB";
+		info->description = "Decrypt a test string using AES-128 and ECB";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_status_update(test, "Executing AES-ECB decryption test\n");
+
+	ast_test_capture_init(&cap);
+
+	if (!ast_check_command_in_path(command)) {
+		ast_test_status_update(test, "couldn't find %s\n", command);
+		return res;
+	}
+
+	args[KEY] = hexstring(key, sizeof(key));
+	if (ast_test_capture_command(&cap, command, args, (const char *)plaintext, sizeof(plaintext)) != 1) {
+		ast_test_status_update(test, "ast_test_capture_command() failed\n");
+		goto cleanup;
+	}
+
+	if (cap.outlen != sizeof(buf)) {
+		ast_test_status_update(test, "Unexpected length for stdout: %zu\n", cap.outlen);
+		goto cleanup;
+	}
+
+	if (cap.errlen != 0) {
+		ast_test_status_update(test, "Unexpected value/length for stderr: '%.*s'\n", (int) cap.errlen, cap.errbuf);
+		goto cleanup;
+	}
+
+	if (cap.pid == -1) {
+		ast_test_status_update(test, "Invalid process id\n");
+		goto cleanup;
+	}
+
+	if (cap.exitcode != 0) {
+		ast_test_status_update(test, "Child exited %d\n", cap.exitcode);
+		goto cleanup;
+	}
+
+	memset(buf, 0, sizeof(buf));
+	ast_aes_set_decrypt_key(key, &aes_key);
+	if (ast_aes_decrypt((const unsigned char *)cap.outbuf, buf, &aes_key) <= 0) {
+		ast_test_status_update(test, "ast_aes_decrypt() failed\n");
+		goto cleanup;
+	}
+
+	if (memcmp(plaintext, buf, sizeof(plaintext))) {
+		ast_test_status_update(test, "AES decryption mismatch\n");
+		goto cleanup;
+	}
+
+	res = AST_TEST_PASS;
+
+cleanup:
+	ast_free(args[KEY]);
+	ast_test_capture_free(&cap);
+	return res;
+}
+
+static int unload_module(void)
+{
+	AST_TEST_UNREGISTER(crypto_rsa_encrypt);
+	AST_TEST_UNREGISTER(crypto_rsa_decrypt);
+	AST_TEST_UNREGISTER(crypto_sign);
+	AST_TEST_UNREGISTER(crypto_verify);
+	AST_TEST_UNREGISTER(crypto_aes_encrypt);
+	AST_TEST_UNREGISTER(crypto_aes_decrypt);
+	return 0;
+}
+
+static int load_module(void)
+{
+	AST_TEST_REGISTER(crypto_rsa_encrypt);
+	AST_TEST_REGISTER(crypto_rsa_decrypt);
+	AST_TEST_REGISTER(crypto_sign);
+	AST_TEST_REGISTER(crypto_verify);
+	AST_TEST_REGISTER(crypto_aes_encrypt);
+	AST_TEST_REGISTER(crypto_aes_decrypt);
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Crypto test module",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.requires = "res_crypto",
+);
diff --git a/tests/test_json.c b/tests/test_json.c
index a14ac3a217c3e055599fb3cda0dbddf19d08df13..e1fc0ba29f0da7e0376a636d35710eae7b66ac92 100644
--- a/tests/test_json.c
+++ b/tests/test_json.c
@@ -41,6 +41,7 @@
 #include "asterisk/json.h"
 #include "asterisk/module.h"
 #include "asterisk/test.h"
+#include "asterisk/file.h"
 
 #include <stdio.h>
 #include <unistd.h>
@@ -1269,27 +1270,6 @@ static int safe_fclose(FILE *f)
 	return 0;
 }
 
-static FILE *mkstemp_file(char *template, const char *mode)
-{
-	int fd = mkstemp(template);
-	FILE *file;
-
-	if (fd < 0) {
-		ast_log(LOG_ERROR, "Failed to create temp file: %s\n",
-			strerror(errno));
-		return NULL;
-	}
-
-	file = fdopen(fd, mode);
-	if (!file) {
-		ast_log(LOG_ERROR, "Failed to create temp file: %s\n",
-			strerror(errno));
-		return NULL;
-	}
-
-	return file;
-}
-
 AST_TEST_DEFINE(json_test_dump_load_file)
 {
 	RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref);
@@ -1312,7 +1292,7 @@ AST_TEST_DEFINE(json_test_dump_load_file)
 
 	/* dump/load file */
 	expected = ast_json_pack("{ s: i }", "one", 1);
-	file = mkstemp_file(filename, "w");
+	file = ast_file_mkftemp(filename, 0644);
 	ast_test_validate(test, NULL != file);
 	uut_res = ast_json_dump_file(expected, file);
 	ast_test_validate(test, 0 == uut_res);
@@ -1347,7 +1327,7 @@ AST_TEST_DEFINE(json_test_dump_load_new_file)
 
 	/* dump/load filename */
 	expected = ast_json_pack("{ s: i }", "one", 1);
-	file = mkstemp_file(filename, "w");
+	file = ast_file_mkftemp(filename, 0644);
 	ast_test_validate(test, NULL != file);
 	uut_res = ast_json_dump_new_file(expected, filename);
 	ast_test_validate(test, 0 == uut_res);
@@ -1378,7 +1358,7 @@ AST_TEST_DEFINE(json_test_dump_load_null)
 	/* dump/load NULL tests */
 	uut = ast_json_load_string("{ \"one\": 1 }", NULL);
 	ast_test_validate(test, NULL != uut);
-	file = mkstemp_file(filename, "w");
+	file = ast_file_mkftemp(filename, 0644);
 	ast_test_validate(test, NULL != file);
 	ast_test_validate(test, NULL == ast_json_dump_string(NULL));
 	ast_test_validate(test, -1 == ast_json_dump_file(NULL, file));
diff --git a/tests/test_mwi.c b/tests/test_mwi.c
index 3f633b37271454e9cca08ec724c086fd759708d1..c9999633b443ef3ed9b87ec0a04fcc9fb2aaf13a 100644
--- a/tests/test_mwi.c
+++ b/tests/test_mwi.c
@@ -63,6 +63,7 @@ static int num_to_mailbox(char *mailbox, size_t size, size_t num)
 
 static int mailbox_to_num(const char *mailbox, size_t *num)
 {
+	uintmax_t tmp;
 	const char *p = strchr(mailbox, '~');
 
 	if (!p) {
@@ -70,10 +71,11 @@ static int mailbox_to_num(const char *mailbox, size_t *num)
 		return -1;
 	}
 
-	if (ast_str_to_umax(++p, num)) {
+	if (ast_str_to_umax(++p, &tmp)) {
 		ast_log(LOG_ERROR, "Unable to convert mailbox '%s' to numeric\n", mailbox);
 		return -1;
 	}
+	*num = (size_t) tmp;
 
 	return 0;
 }
diff --git a/tests/test_pbx.c b/tests/test_pbx.c
index d4858e4a2196633ca21af7c42d0dbdad2fee9a05..3986d7d05881c07a0a6aae075dbe36e11349f229 100644
--- a/tests/test_pbx.c
+++ b/tests/test_pbx.c
@@ -383,8 +383,48 @@ AST_TEST_DEFINE(call_backtrace)
 	return AST_TEST_PASS;
 }
 
+AST_TEST_DEFINE(just_fail)
+{
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "JUST_FAIL";
+		info->category = "/TEST_PASS_FAIL/";
+		info->summary = "Just fails";
+		info->description = "Just fails. "
+			"This test is mainly used for testing CI and tool failure scenarios.";
+		info->explicit_only = 1;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+	ast_test_status_update(test, "This test just forces a fail\n");
+
+	return AST_TEST_FAIL;
+}
+
+AST_TEST_DEFINE(just_pass)
+{
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "JUST_PASS";
+		info->category = "/TEST_PASS_FAIL/";
+		info->summary = "Just passes";
+		info->description = "Just passes. "
+			"This test is mainly used for testing CI and tool failure scenarios.";
+		info->explicit_only = 1;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+	ast_test_status_update(test, "This test just forces a pass\n");
+
+	return AST_TEST_PASS;
+}
+
 static int unload_module(void)
 {
+	AST_TEST_UNREGISTER(just_pass);
+	AST_TEST_UNREGISTER(just_fail);
 	AST_TEST_UNREGISTER(call_backtrace);
 	AST_TEST_UNREGISTER(call_assert);
 	AST_TEST_UNREGISTER(segv);
@@ -398,6 +438,8 @@ static int load_module(void)
 	AST_TEST_REGISTER(segv);
 	AST_TEST_REGISTER(call_assert);
 	AST_TEST_REGISTER(call_backtrace);
+	AST_TEST_REGISTER(just_fail);
+	AST_TEST_REGISTER(just_pass);
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
diff --git a/tests/test_res_rtp.c b/tests/test_res_rtp.c
index 1d36116745c4278c7dfbeb60d91239daee4fad4f..2ecf3833636d05d29e0e1228d7be12b8eb7c265b 100644
--- a/tests/test_res_rtp.c
+++ b/tests/test_res_rtp.c
@@ -36,11 +36,14 @@
 #include "asterisk/rtp_engine.h"
 #include "asterisk/data_buffer.h"
 #include "asterisk/format_cache.h"
+#include <assert.h>
+#include <sched.h>
 
 enum test_type {
 	TEST_TYPE_NONE = 0,	/* No special setup required */
 	TEST_TYPE_NACK,		/* Enable NACK */
 	TEST_TYPE_REMB,		/* Enable REMB */
+	TEST_TYPE_STD_RTCP, /* Let the stack do RTCP */
 };
 
 static void ast_sched_context_destroy_wrapper(struct ast_sched_context *sched)
@@ -54,18 +57,30 @@ static int test_init_rtp_instances(struct ast_rtp_instance **instance1,
 	struct ast_rtp_instance **instance2, struct ast_sched_context *test_sched,
 	enum test_type type)
 {
-	struct ast_sockaddr addr;
+	struct ast_sockaddr addr1;
+	struct ast_sockaddr addr2;
+	enum ast_rtp_instance_rtcp rtcp_type = AST_RTP_INSTANCE_RTCP_MUX;
 
-	ast_sockaddr_parse(&addr, "127.0.0.1", 0);
+	ast_sockaddr_parse(&addr1, "127.0.0.1", 0);
+	ast_sockaddr_parse(&addr2, "127.0.0.1", 0);
 
-	*instance1 = ast_rtp_instance_new("asterisk", test_sched, &addr, NULL);
-	*instance2 = ast_rtp_instance_new("asterisk", test_sched, &addr, NULL);
+	*instance1 = ast_rtp_instance_new("asterisk", test_sched, &addr1, "instance1");
+	*instance2 = ast_rtp_instance_new("asterisk", test_sched, &addr2, "instance2");
 	if (!instance1 || !instance2) {
 		return -1;
 	}
 
-	ast_rtp_instance_set_prop(*instance1, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_MUX);
-	ast_rtp_instance_set_prop(*instance2, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_MUX);
+	ast_rtp_instance_set_channel_id(*instance1, "instance1");
+	ast_rtp_instance_set_channel_id(*instance2, "instance2");
+
+	if (type == TEST_TYPE_STD_RTCP) {
+		rtcp_type = AST_RTP_INSTANCE_RTCP_STANDARD;
+	}
+
+	ast_rtp_instance_set_prop(*instance1,
+		AST_RTP_PROPERTY_RTCP, rtcp_type);
+	ast_rtp_instance_set_prop(*instance2,
+		AST_RTP_PROPERTY_RTCP, rtcp_type);
 
 	if (type == TEST_TYPE_NACK) {
 		ast_rtp_instance_set_prop(*instance1, AST_RTP_PROPERTY_RETRANS_RECV, 1);
@@ -77,11 +92,11 @@ static int test_init_rtp_instances(struct ast_rtp_instance **instance1,
 		ast_rtp_instance_set_prop(*instance2, AST_RTP_PROPERTY_REMB, 1);
 	}
 
-	ast_rtp_instance_get_local_address(*instance1, &addr);
-	ast_rtp_instance_set_remote_address(*instance2, &addr);
+	ast_rtp_instance_get_local_address(*instance1, &addr1);
+	ast_rtp_instance_set_remote_address(*instance2, &addr1);
 
-	ast_rtp_instance_get_local_address(*instance2, &addr);
-	ast_rtp_instance_set_remote_address(*instance1, &addr);
+	ast_rtp_instance_get_local_address(*instance2, &addr2);
+	ast_rtp_instance_set_remote_address(*instance1, &addr2);
 
 	ast_rtp_instance_reset_test_engine(*instance1);
 
@@ -130,6 +145,120 @@ static void test_write_and_read_frames(struct ast_rtp_instance *instance1,
 	test_read_frames(instance2, num);
 }
 
+
+/*
+ * Unfortunately, we can't use usleep() to create
+ * packet spacing because there are signals in use
+ * which cause usleep to immediately return.  Instead
+ * we have to spin.  :(
+ */
+static void SLEEP_SPINNER(int ms)
+{
+	struct timeval a = ast_tvnow();
+
+	while(1) {
+		sched_yield();
+		if (ast_remaining_ms(a, ms) <= 0) {
+			break;
+		}
+	}
+}
+
+/*
+ * This function is NOT really a reliable implementation.
+ * Its purpose is only to aid in code development in res_rtp_asterisk.
+ */
+static void test_write_and_read_interleaved_frames(struct ast_rtp_instance *instance1,
+	struct ast_rtp_instance *instance2, int howlong, int rtcp_interval)
+{
+	char data[320] = "";
+	int pktinterval = 20;
+
+	struct ast_frame frame_out1 = {
+		.frametype = AST_FRAME_VOICE,
+		.subclass.format = ast_format_ulaw,
+		.seqno = 4556,
+		.data.ptr = data,
+		.datalen = 160,
+		.samples = 1,
+		.len = pktinterval,
+		.ts = 4622295,
+	};
+	struct ast_frame frame_out2 = {
+		.frametype = AST_FRAME_VOICE,
+		.subclass.format = ast_format_ulaw,
+		.seqno = 6554,
+		.data.ptr = data,
+		.datalen = 160,
+		.samples = 1,
+		.len = pktinterval,
+		.ts = 8622295,
+	};
+	struct ast_frame *frame_in1;
+	struct ast_frame *frame_in2;
+	int index;
+	int num;
+	int rtcpnum;
+	int reverse = 1;
+	int send_rtcp = 0;
+
+	num = howlong / pktinterval;
+
+	rtcpnum = rtcp_interval / pktinterval;
+
+	ast_set_flag(&frame_out1, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
+	ast_set_flag(&frame_out1, AST_FRFLAG_HAS_TIMING_INFO);
+	ast_set_flag(&frame_out2, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
+	ast_set_flag(&frame_out2, AST_FRFLAG_HAS_TIMING_INFO);
+
+	for (index = 0; index < num; index++) {
+		struct timeval start = ast_tvnow();
+		time_t ms;
+
+		if (index == 1) {
+			ast_clear_flag(&frame_out1, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
+			ast_clear_flag(&frame_out1, AST_FRFLAG_HAS_TIMING_INFO);
+			ast_clear_flag(&frame_out2, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
+			ast_clear_flag(&frame_out2, AST_FRFLAG_HAS_TIMING_INFO);
+		}
+		frame_out1.seqno += index;
+		frame_out1.delivery = start;
+		frame_out1.ts += frame_out1.len;
+		ast_rtp_instance_write(instance1, &frame_out1);
+
+		if (send_rtcp && index && (index % rtcpnum == 0)) {
+			ast_rtp_instance_queue_report(instance1);
+		}
+
+		frame_in2 = ast_rtp_instance_read(instance2, 0);
+		ast_frfree(frame_in2);
+		frame_in2 = ast_rtp_instance_read(instance2, 1);
+		ast_frfree(frame_in2);
+
+		if (reverse) {
+			frame_out2.seqno += index;
+			frame_out2.delivery = ast_tvnow();
+			frame_out2.ts += frame_out2.len;
+			ast_rtp_instance_write(instance2, &frame_out2);
+
+			if (send_rtcp && index && (index % rtcpnum == 0)) {
+				ast_rtp_instance_queue_report(instance2);
+			}
+
+			frame_in1 = ast_rtp_instance_read(instance1, 0);
+			ast_frfree(frame_in1);
+			frame_in1 = ast_rtp_instance_read(instance1, 1);
+			ast_frfree(frame_in1);
+
+		}
+
+		ms = frame_out1.len - ast_tvdiff_ms(ast_tvnow(),start);
+		ms += (index % 2 ? 5 : 12);
+		ms += (index % 3 ? 2 : 30);
+		SLEEP_SPINNER(ms);
+	}
+}
+
 AST_TEST_DEFINE(nack_no_packet_loss)
 {
 	RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ast_rtp_instance_destroy);
@@ -523,8 +652,47 @@ AST_TEST_DEFINE(fir_nominal)
 	return AST_TEST_PASS;
 }
 
+/*
+ * This test should not normally be run.  Its only purpose is to
+ * aid in code development.
+ */
+AST_TEST_DEFINE(mes)
+{
+	RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ast_rtp_instance_destroy);
+	RAII_VAR(struct ast_rtp_instance *, instance2, NULL, ast_rtp_instance_destroy);
+	RAII_VAR(struct ast_sched_context *, test_sched, NULL, ast_sched_context_destroy_wrapper);
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "mes";
+		info->category = "/res/res_rtp/";
+		info->summary = "Media Experience Score";
+		info->description =
+			"Tests calculation of Media Experience Score (only run by explicit request)";
+		info->explicit_only = 1;
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	test_sched = ast_sched_context_create();
+	ast_sched_start_thread(test_sched);
+
+	if ((test_init_rtp_instances(&instance1, &instance2,
+		test_sched, TEST_TYPE_NONE)) < 0) {
+		ast_log(LOG_ERROR, "Failed to initialize test!\n");
+		return AST_TEST_FAIL;
+	}
+
+	test_write_and_read_interleaved_frames(
+		instance1, instance2, 1000, 5000);
+
+	return AST_TEST_PASS;
+}
+
 static int unload_module(void)
 {
+	AST_TEST_UNREGISTER(mes);
 	AST_TEST_UNREGISTER(nack_no_packet_loss);
 	AST_TEST_UNREGISTER(nack_nominal);
 	AST_TEST_UNREGISTER(nack_overflow);
@@ -544,6 +712,7 @@ static int load_module(void)
 	AST_TEST_REGISTER(remb_nominal);
 	AST_TEST_REGISTER(sr_rr_nominal);
 	AST_TEST_REGISTER(fir_nominal);
+	AST_TEST_REGISTER(mes);
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
diff --git a/tests/test_stasis.c b/tests/test_stasis.c
index 5efb1ec1e6cdb7b720dcea2e040dc1ecb75fe042..71026a6cced3cc6e7d0dc8be64316f8ba71badda 100644
--- a/tests/test_stasis.c
+++ b/tests/test_stasis.c
@@ -2195,7 +2195,7 @@ static void dump_consumer(struct ast_test *test, struct cts *cts)
 	int i;
 	struct stasis_subscription_change *data;
 
-	ast_test_status_update(test, "Messages received: %ld  Final? %s\n", cts->consumer->messages_rxed_len,
+	ast_test_status_update(test, "Messages received: %zu  Final? %s\n", cts->consumer->messages_rxed_len,
 		cts->consumer->complete ? "yes" : "no");
 	for (i = 0; i < cts->consumer->messages_rxed_len; i++) {
 		data = stasis_message_data(cts->consumer->messages_rxed[i]);
diff --git a/tests/test_stasis_channels.c b/tests/test_stasis_channels.c
index d4a05b206f98ff625d100c71fe0f66e5aed09148..5ea9dd807c3e8b8b7d6dd8b5b5c63fd207374370 100644
--- a/tests/test_stasis_channels.c
+++ b/tests/test_stasis_channels.c
@@ -273,7 +273,7 @@ AST_TEST_DEFINE(channel_snapshot_json)
 	ast_test_validate(test, NULL != snapshot);
 
 	actual = ast_channel_snapshot_to_json(snapshot, NULL);
-	expected = ast_json_pack("{ s: s, s: s, s: s, s: s,"
+	expected = ast_json_pack("{ s: s, s: s, s: s, s: s, s: s,"
 				 "  s: { s: s, s: s, s: i, s: s, s: s },"
 				 "  s: { s: s, s: s },"
 				 "  s: { s: s, s: s },"
@@ -284,6 +284,7 @@ AST_TEST_DEFINE(channel_snapshot_json)
 				 "state", "Down",
 				 "accountcode", "acctcode",
 				 "id", ast_channel_uniqueid(chan),
+				 "protocol_id", "",
 				 "dialplan",
 				 "context", "context",
 				 "exten", "exten",
diff --git a/tests/test_stasis_state.c b/tests/test_stasis_state.c
index 3ad450d111100cccb601a90786815e2b3476d956..3dc53345e55c1bed85b5925686b7276d6ab32f4b 100644
--- a/tests/test_stasis_state.c
+++ b/tests/test_stasis_state.c
@@ -63,11 +63,13 @@ static int expect_null;
 static int validate_data(const char *id, struct foo_data *foo)
 {
 	size_t num;
+	uintmax_t tmp;
 
-	if (ast_str_to_umax(id, &num)) {
+	if (ast_str_to_umax(id, &tmp)) {
 		ast_log(LOG_ERROR, "Unable to convert the state's id '%s' to numeric\n", id);
 		return -1;
 	}
+	num = (size_t) tmp;
 
 	running_total += num;
 
@@ -247,6 +249,7 @@ static struct stasis_message *create_foo_type_message(const char *id)
 {
 	struct stasis_message *msg;
 	struct foo_data *foo;
+	uintmax_t tmp;
 
 	foo = ao2_alloc(sizeof(*foo), NULL);
 	if (!foo) {
@@ -254,11 +257,12 @@ static struct stasis_message *create_foo_type_message(const char *id)
 		return NULL;
 	}
 
-	if (ast_str_to_umax(id, &foo->bar)) {
+	if (ast_str_to_umax(id, &tmp)) {
 		ast_log(LOG_ERROR, "Unable to convert the state's id '%s' to numeric\n", id);
 		ao2_ref(foo, -1);
 		return NULL;
 	}
+	foo->bar = (size_t) tmp;
 
 	msg = stasis_message_create_full(foo_type(), foo, NULL);
 	if (!msg) {
diff --git a/tests/test_strings.c b/tests/test_strings.c
index f3c7e56408fc19ef85584468c4efc33c637884ee..a12105640505d907632515cdfec1b13e5c7c4dfa 100644
--- a/tests/test_strings.c
+++ b/tests/test_strings.c
@@ -385,6 +385,143 @@ AST_TEST_DEFINE(strsep_test)
 	return AST_TEST_PASS;
 }
 
+AST_TEST_DEFINE(strsep_quoted_test)
+{
+	char *test1, *test2, *test3;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "strsep_quoted";
+		info->category = "/main/strings/";
+		info->summary = "Test ast_strsep_quoted";
+		info->description = "Test ast_strsep_quoted";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	test1 = ast_strdupa("ghi=jkl,mno=\"pqr,stu\",abc=def, vwx = yz1 ,  vwx = yz1 ,  "
+		"\" vwx = yz1 \" ,  \" vwx , yz1 \",v'w'x, \"'x,v','x'\" , \" i\\'m a test\""
+		", \" i\\'m a, test\", \" i\\'m a, test\", e\\,nd, end\\");
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', 0);
+	ast_test_validate(test, 0 == strcmp("ghi=jkl", test2));
+
+	test3 = ast_strsep_quoted(&test2, '=', '"', 0);
+	ast_test_validate(test, 0 == strcmp("ghi", test3));
+
+	test3 = ast_strsep_quoted(&test2, '=', '"', 0);
+	ast_test_validate(test, 0 == strcmp("jkl", test3));
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', 0);
+	ast_test_validate(test, 0 == strcmp("mno=\"pqr,stu\"", test2));
+
+	test3 = ast_strsep_quoted(&test2, '=', '"', 0);
+	ast_test_validate(test, 0 == strcmp("mno", test3));
+
+	test3 = ast_strsep_quoted(&test2, '=', '"', 0);
+	ast_test_validate(test, 0 == strcmp("\"pqr,stu\"", test3));
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', 0);
+	ast_test_validate(test, 0 == strcmp("abc=def", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', 0);
+	ast_test_validate(test, 0 == strcmp(" vwx = yz1 ", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_TRIM);
+	ast_test_validate(test, 0 == strcmp("vwx = yz1", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_STRIP);
+	ast_test_validate(test, 0 == strcmp(" vwx = yz1 ", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_STRIP | AST_STRSEP_TRIM);
+	ast_test_validate(test, 0 == strcmp("vwx , yz1", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_STRIP | AST_STRSEP_TRIM);
+	ast_test_validate(test, 0 == strcmp("v'w'x", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_TRIM);
+	ast_test_validate(test, 0 == strcmp("\"'x,v','x'\"", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_TRIM);
+	ast_test_validate(test, 0 == strcmp("\" i\\'m a test\"", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE);
+	ast_test_validate(test, 0 == strcmp("\" i'm a, test\"", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_ALL);
+	ast_test_validate(test, 0 == strcmp("i'm a, test", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE);
+	ast_test_validate(test, 0 == strcmp("e,nd", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '"', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE);
+	ast_test_validate(test, 0 == strcmp("end", test2));
+
+	// Now use '|' as the quote character
+	test1 = ast_strdupa("ghi=jkl,mno=|pqr,stu|,abc=def, vwx = yz1 ,  vwx = yz1 ,  "
+		"| vwx = yz1 | ,  | vwx , yz1 |,v'w'x, |'x,v','x'| , | i\\'m a test|"
+		", | i\\'m a, test|, | i\\'m a, test|, e\\,nd, end\\");
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', 0);
+	ast_test_validate(test, 0 == strcmp("ghi=jkl", test2));
+
+	test3 = ast_strsep_quoted(&test2, '=', '|', 0);
+	ast_test_validate(test, 0 == strcmp("ghi", test3));
+
+	test3 = ast_strsep_quoted(&test2, '=', '|', 0);
+	ast_test_validate(test, 0 == strcmp("jkl", test3));
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', 0);
+	ast_test_validate(test, 0 == strcmp("mno=|pqr,stu|", test2));
+
+	test3 = ast_strsep_quoted(&test2, '=', '|', 0);
+	ast_test_validate(test, 0 == strcmp("mno", test3));
+
+	test3 = ast_strsep_quoted(&test2, '=', '|', 0);
+	ast_test_validate(test, 0 == strcmp("|pqr,stu|", test3));
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', 0);
+	ast_test_validate(test, 0 == strcmp("abc=def", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', 0);
+	ast_test_validate(test, 0 == strcmp(" vwx = yz1 ", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_TRIM);
+	ast_test_validate(test, 0 == strcmp("vwx = yz1", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_STRIP);
+	ast_test_validate(test, 0 == strcmp(" vwx = yz1 ", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_STRIP | AST_STRSEP_TRIM);
+	ast_test_validate(test, 0 == strcmp("vwx , yz1", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_STRIP | AST_STRSEP_TRIM);
+	ast_test_validate(test, 0 == strcmp("v'w'x", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_TRIM);
+	ast_test_validate(test, 0 == strcmp("|'x,v','x'|", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_TRIM);
+	ast_test_validate(test, 0 == strcmp("| i\\'m a test|", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE);
+	ast_test_validate(test, 0 == strcmp("| i'm a, test|", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_ALL);
+	ast_test_validate(test, 0 == strcmp("i'm a, test", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE);
+	ast_test_validate(test, 0 == strcmp("e,nd", test2));
+
+	test2 = ast_strsep_quoted(&test1, ',', '|', AST_STRSEP_TRIM | AST_STRSEP_UNESCAPE);
+	ast_test_validate(test, 0 == strcmp("end", test2));
+
+
+	// nothing failed; we're all good!
+	return AST_TEST_PASS;
+}
+
 static int test_semi(char *string1, char *string2, int test_len)
 {
 	char *test2 = NULL;
@@ -740,6 +877,7 @@ static int unload_module(void)
 	AST_TEST_UNREGISTER(begins_with_test);
 	AST_TEST_UNREGISTER(ends_with_test);
 	AST_TEST_UNREGISTER(strsep_test);
+	AST_TEST_UNREGISTER(strsep_quoted_test);
 	AST_TEST_UNREGISTER(escape_semicolons_test);
 	AST_TEST_UNREGISTER(escape_test);
 	AST_TEST_UNREGISTER(strings_match);
@@ -754,6 +892,7 @@ static int load_module(void)
 	AST_TEST_REGISTER(begins_with_test);
 	AST_TEST_REGISTER(ends_with_test);
 	AST_TEST_REGISTER(strsep_test);
+	AST_TEST_REGISTER(strsep_quoted_test);
 	AST_TEST_REGISTER(escape_semicolons_test);
 	AST_TEST_REGISTER(escape_test);
 	AST_TEST_REGISTER(strings_match);
diff --git a/tests/test_vector.c b/tests/test_vector.c
index 2dfcc60a8c61018b5ce89b3e64224f903f8431c0..5f6f0a54b395b0c52716d05047d0aa9558a9f937 100644
--- a/tests/test_vector.c
+++ b/tests/test_vector.c
@@ -48,7 +48,7 @@ static void cleanup(char *element)
 }
 
 #define STRING_CMP(a, b) ({ \
-	((a) == NULL || (b) == NULL) ? -1 : (strcmp((a), (b)) == 0); \
+	((void *)(a) == (void *)NULL || (void *)(b) == (void *)NULL) ? -1 : (strcmp((a), (b)) == 0); \
 })
 
 AST_TEST_DEFINE(basic_ops)
diff --git a/third-party/pjproject/configure.m4 b/third-party/pjproject/configure.m4
index 2f266252da9c3735b507b73577a651d99e249288..b16738c9ac738fe3b7497a42d87fb56aa4b18610 100644
--- a/third-party/pjproject/configure.m4
+++ b/third-party/pjproject/configure.m4
@@ -74,12 +74,16 @@ AC_DEFUN([_PJPROJECT_CONFIGURE],
 			y|ye|yes)
 			# Not to mention SSL is the default in PJProject and means "autodetect".
 			# In Asterisk, "./configure --with-ssl" means "must be present".
-			PJPROJECT_CONFIGURE_OPTS="${PJPROJECT_CONFIGURE_OPTS}"
+			PJPROJECT_CONFIGURE_OPTS="${PJPROJECT_CONFIGURE_OPTS} --with-ssl"
 			;;
 			*)
 			PJPROJECT_CONFIGURE_OPTS="${PJPROJECT_CONFIGURE_OPTS} --with-ssl=${with_ssl}"
 			;;
 			esac
+		else
+			if test $PBX_OPENSSL -eq 1 ; then
+				PJPROJECT_CONFIGURE_OPTS="${PJPROJECT_CONFIGURE_OPTS} --with-ssl"
+			fi
 		fi
 
 		# Determine if we're doing an out-of-tree build...
@@ -131,6 +135,8 @@ AC_DEFUN([_PJPROJECT_CONFIGURE],
 		AC_DEFINE([HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE], 1, [Define if your system has HAVE_PJSIP_TRANSPORT_DISABLE_CONNECTION_REUSE declared])
 		AC_DEFINE([HAVE_PJSIP_OAUTH_AUTHENTICATION], 1, [Define if your system has HAVE_PJSIP_OAUTH_AUTHENTICATION declared])
 		AC_DEFINE([HAVE_PJPROJECT_ON_VALID_ICE_PAIR_CALLBACK], 1, [Define if your system has the on_valid_pair pjnath callback.])
+		AC_DEFINE([HAVE_PJSIP_TLS_TRANSPORT_RESTART], 1, [Define if your system has pjsip_tls_transport_restart support.])
+		AC_DEFINE([HAVE_PJSIP_EVSUB_PENDING_NOTIFY], 1, [Define to 1 if evsub requires a NOTIFY on SUBSCRIBE.])
 
 		AC_SUBST([PJPROJECT_BUNDLED])
 		AC_SUBST([PJPROJECT_BUNDLED_OOT])
diff --git a/third-party/pjproject/patches/0000-configure-ssl-library-path.patch b/third-party/pjproject/patches/0000-configure-ssl-library-path.patch
index 3c1f7498d887ca8f9ce2c158e58e17d131101f4c..c4dbb49e62f7a77ce8f252ea1e7c970972f53926 100644
--- a/third-party/pjproject/patches/0000-configure-ssl-library-path.patch
+++ b/third-party/pjproject/patches/0000-configure-ssl-library-path.patch
@@ -1,19 +1,9 @@
-From e8000cc80e5f8ba02cc52852edc02cdb0e949525 Mon Sep 17 00:00:00 2001
-From: Richard Mudgett <rmudgett@digium.com>
-Date: Mon, 6 Aug 2018 11:24:25 -0500
-Subject: [PATCH 1/5] 0000-configure-ssl-library-path.patch
-
----
- aconfigure    | 6 +++++-
- aconfigure.ac | 6 +++++-
- 2 files changed, 10 insertions(+), 2 deletions(-)
-
 diff --git a/aconfigure b/aconfigure
-index 1c449b8..c4c6060 100755
+index d6f0e8809..9dcd46398 100755
 --- a/aconfigure
 +++ b/aconfigure
-@@ -7954,7 +7954,11 @@ else
-                 if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then
+@@ -8986,7 +8986,11 @@ else $as_nop
+ 	        if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then
                      CFLAGS="$CFLAGS -I$with_ssl/include"
                      CPPFLAGS="$CPPFLAGS -I$with_ssl/include"
 -                    LDFLAGS="$LDFLAGS -L$with_ssl/lib"
@@ -22,15 +12,15 @@ index 1c449b8..c4c6060 100755
 +                    else
 +                        LDFLAGS="$LDFLAGS -L$with_ssl"
 +                    fi
-                     { $as_echo "$as_me:${as_lineno-$LINENO}: result: Using SSL prefix... $with_ssl" >&5
- $as_echo "Using SSL prefix... $with_ssl" >&6; }
+                     { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: Using SSL prefix... $with_ssl" >&5
+ printf "%s\n" "Using SSL prefix... $with_ssl" >&6; }
                  fi
 diff --git a/aconfigure.ac b/aconfigure.ac
-index 2c272cd..a5d6d97 100644
+index 16b311045..849da81ab 100644
 --- a/aconfigure.ac
 +++ b/aconfigure.ac
-@@ -1580,7 +1580,11 @@ AC_ARG_ENABLE(ssl,
-                 if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then
+@@ -1838,7 +1838,11 @@ AC_ARG_ENABLE(ssl,
+ 	        if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then
                      CFLAGS="$CFLAGS -I$with_ssl/include"
                      CPPFLAGS="$CPPFLAGS -I$with_ssl/include"
 -                    LDFLAGS="$LDFLAGS -L$with_ssl/lib"
@@ -42,6 +32,3 @@ index 2c272cd..a5d6d97 100644
                      AC_MSG_RESULT([Using SSL prefix... $with_ssl])
                  fi
  
--- 
-2.7.4
-
diff --git a/third-party/pjproject/patches/0000-remove-third-party.patch b/third-party/pjproject/patches/0000-remove-third-party.patch
index f25aeac176a348b8837bd7bdb59f280ef666e3da..ab6c6d1ec6fc409c74934dc7942700b819bf99e0 100644
--- a/third-party/pjproject/patches/0000-remove-third-party.patch
+++ b/third-party/pjproject/patches/0000-remove-third-party.patch
@@ -1,14 +1,5 @@
-From 665a2fbc3a09a71cd77988ae2deb3f5d3e205f63 Mon Sep 17 00:00:00 2001
-From: Richard Mudgett <rmudgett@digium.com>
-Date: Thu, 23 Feb 2017 17:10:07 -0600
-Subject: [PATCH 2/5] 0000-remove-third-party.patch
-
----
- build.mak.in | 97 ------------------------------------------------------------
- 1 file changed, 97 deletions(-)
-
 diff --git a/build.mak.in b/build.mak.in
-index 80ccad1..41ec64e 100644
+index 4bc464f8c..80681d961 100644
 --- a/build.mak.in
 +++ b/build.mak.in
 @@ -1,4 +1,3 @@
@@ -16,10 +7,11 @@ index 80ccad1..41ec64e 100644
  include $(PJDIR)/version.mak
  export PJ_DIR := $(PJDIR)
  
-@@ -37,19 +36,6 @@ export APP_THIRD_PARTY_EXT :=
+@@ -42,21 +41,6 @@ export APP_THIRD_PARTY_EXT :=
  export APP_THIRD_PARTY_LIBS :=
  export APP_THIRD_PARTY_LIB_FILES :=
  
+-ifneq (@ac_no_srtp@,1)
 -ifneq (@ac_external_srtp@,0)
 -# External SRTP library
 -APP_THIRD_PARTY_EXT += -l@ac_external_srtp_lib@
@@ -32,11 +24,12 @@ index 80ccad1..41ec64e 100644
 -APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libsrtp.$(SHLIB_SUFFIX).$(PJ_VERSION_MAJOR) $(PJ_DIR)/third_party/lib/libsrtp.$(SHLIB_SUFFIX)
 -endif
 -endif
+-endif
 -
  ifeq (@ac_pjmedia_resample@,libresample)
  APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libresample-$(LIB_SUFFIX)
  ifeq ($(PJ_SHARED_LIBRARIES),)
-@@ -66,89 +52,6 @@ APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libresample.$(SHLIB_SUFFI
+@@ -73,102 +57,6 @@ APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libresample.$(SHLIB_SUFFI
  endif
  endif
  
@@ -122,10 +115,20 @@ index 80ccad1..41ec64e 100644
 -endif
 -endif
 -
+-ifneq (@ac_no_webrtc_aec3@,1)
+-ifeq (@ac_external_webrtc_aec3@,1)
+-APP_THIRD_PARTY_EXT += -lwebrtc-aec3
+-else
+-APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libwebrtc-aec3-$(LIB_SUFFIX)
+-ifeq ($(PJ_SHARED_LIBRARIES),)
+-APP_THIRD_PARTY_LIBS += -lwebrtc-aec3-$(TARGET_NAME)
+-else
+-APP_THIRD_PARTY_LIBS += -lwebrtc-aec3
+-APP_THIRD_PARTY_LIB_FILES += $(PJ_DIR)/third_party/lib/libwebrtc-aec3.$(SHLIB_SUFFIX).$(PJ_VERSION_MAJOR) $(PJ_DIR)/third_party/lib/libwebrtc.$(SHLIB_SUFFIX)
+-endif
+-endif
+-endif
 -
+
  # Additional flags
  @ac_build_mak_vars@
- 
--- 
-2.7.4
-
diff --git a/third-party/pjproject/patches/0000-set_apps_initial_log_level.patch b/third-party/pjproject/patches/0000-set_apps_initial_log_level.patch
deleted file mode 100644
index b1fe02c0d5c765360fec970651145f26dd36c35a..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0000-set_apps_initial_log_level.patch
+++ /dev/null
@@ -1,53 +0,0 @@
-From c40ad6ba454fdf6456d8ffa92faa4cd49f2c807d Mon Sep 17 00:00:00 2001
-From: Richard Mudgett <rmudgett@digium.com>
-Date: Thu, 23 Feb 2017 17:11:00 -0600
-Subject: [PATCH 3/5] 0000-set_apps_initial_log_level.patch
-
----
- pjsip-apps/src/pjsua/main.c             | 2 ++
- pjsip-apps/src/pjsystest/main_console.c | 2 ++
- pjsip-apps/src/python/_pjsua.c          | 3 ++-
- 3 files changed, 6 insertions(+), 1 deletion(-)
-
-diff --git a/pjsip-apps/src/pjsua/main.c b/pjsip-apps/src/pjsua/main.c
-index 2baaf82..11831f2 100644
---- a/pjsip-apps/src/pjsua/main.c
-+++ b/pjsip-apps/src/pjsua/main.c
-@@ -126,5 +126,7 @@ int main_func(int argc, char *argv[])
- 
- int main(int argc, char *argv[])
- {
-+    pj_log_set_level(1);
-+
-     return pj_run_app(&main_func, argc, argv, 0);
- }
-diff --git a/pjsip-apps/src/pjsystest/main_console.c b/pjsip-apps/src/pjsystest/main_console.c
-index 122cdc7..dc79eab 100644
---- a/pjsip-apps/src/pjsystest/main_console.c
-+++ b/pjsip-apps/src/pjsystest/main_console.c
-@@ -133,6 +133,8 @@ void gui_sleep(unsigned sec)
- 
- int main()
- {
-+    pj_log_set_level(1);
-+
-     if (systest_init() != PJ_SUCCESS)
- 	return 1;
- 
-diff --git a/pjsip-apps/src/python/_pjsua.c b/pjsip-apps/src/python/_pjsua.c
-index 31b835e..3e15030 100644
---- a/pjsip-apps/src/python/_pjsua.c
-+++ b/pjsip-apps/src/python/_pjsua.c
-@@ -4434,7 +4434,8 @@ init_pjsua(void)
-     PyObject* m = NULL;
- #define ADD_CONSTANT(mod,name)	PyModule_AddIntConstant(mod,#name,name)
- 
--    
-+    pj_log_set_level(1);
-+
-     PyEval_InitThreads();
- 
-     if (PyType_Ready(&PyTyp_pjsua_callback) < 0)
--- 
-2.7.4
-
diff --git a/third-party/pjproject/patches/0000-solaris.patch b/third-party/pjproject/patches/0000-solaris.patch
deleted file mode 100644
index 155cdbee5e377cc068c3575f3754eb1bd9ec884f..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0000-solaris.patch
+++ /dev/null
@@ -1,135 +0,0 @@
-From 1ac599a0f29500a15faf0dbbdc2565cc7dce2420 Mon Sep 17 00:00:00 2001
-From: Shaun Ruffell <sruffell@digium.com>
-Date: Fri, 7 Sep 2012 14:31:19 -0500
-Subject: [PATCH 4/5] pjproject: Fix for Solaris builds. Do not undef s_addr.
-
-pjproject, in order to solve build problems on Windows [1], undefines s_addr in
-one of it's headers that is included in res_rtp_asterisk.c. On Solaris s_addr is
-not a structure member, but defined to map to the real strucuture member,
-therefore when building on Solaris it's possible to get build errors like:
-
-    [CC] res_rtp_asterisk.c -> res_rtp_asterisk.o
-    In file included from /export/home/admin/asterisk-11-svn/include/asterisk/stun.h:29,
-                     from res_rtp_asterisk.c:51:
-    /export/home/admin/asterisk-11-svn/include/asterisk/network.h: In function `inaddrcmp':
-    /export/home/admin/asterisk-11-svn/include/asterisk/network.h:92: error: structure has no member named `s_addr'
-    /export/home/admin/asterisk-11-svn/include/asterisk/network.h:92: error: structure has no member named `s_addr'
-    res_rtp_asterisk.c: In function `ast_rtp_on_ice_tx_pkt':
-    res_rtp_asterisk.c:706: warning: dereferencing type-punned pointer will break strict-aliasing rules
-    res_rtp_asterisk.c:710: warning: dereferencing type-punned pointer will break strict-aliasing rules
-    res_rtp_asterisk.c: In function `rtp_add_candidates_to_ice':
-    res_rtp_asterisk.c:1085: error: structure has no member named `s_addr'
-    make[2]: *** [res_rtp_asterisk.o] Error 1
-    make[1]: *** [res] Error 2
-    make[1]: Leaving directory `/export/home/admin/asterisk-11-svn'
-    gmake: *** [_cleantest_all] Error 2
-
-Unfortunately, in order to make this work, I also had to make sure pjproject
-only used the typdef pj_in_addr and not the struct pj_in_addr so that when
-building Asterisk I could "typedef struct in_addr pj_in_addr". It's possible
-then that the library and users of those interfaces in Asterisk have a different
-idea about the type of the argument. While on the surface it looks like they are
-all 32 bit big endian values.
-
-[1] http://trac.pjsip.org/repos/changeset/484
-
-Reported-by: Ben Klang
-(issues ASTERISK-20366)
-
-Updated by ASTERISK-27997
----
- pjlib/include/pj/sock.h         | 8 +++++++-
- pjlib/src/pj/sock_bsd.c         | 2 +-
- pjlib/src/pj/sock_symbian.cpp   | 2 +-
- pjlib/src/pj/sock_uwp.cpp       | 2 +-
- pjsip/src/test/transport_test.c | 2 +-
- 5 files changed, 11 insertions(+), 5 deletions(-)
-
-diff --git a/pjlib/include/pj/sock.h b/pjlib/include/pj/sock.h
-index 4daf298..c35833c 100644
---- a/pjlib/include/pj/sock.h
-+++ b/pjlib/include/pj/sock.h
-@@ -484,6 +484,7 @@ typedef enum pj_socket_sd_type
-  */
- #define PJ_INVALID_SOCKET   (-1)
- 
-+#ifndef _ASTERISK_H
- /* Must undefine s_addr because of pj_in_addr below */
- #undef s_addr
- 
-@@ -495,6 +496,11 @@ typedef struct pj_in_addr
-     pj_uint32_t	s_addr;		/**< The 32bit IP address.	    */
- } pj_in_addr;
- 
-+#else
-+#include <sys/types.h>
-+#include <netinet/in.h>
-+typedef struct in_addr pj_in_addr;
-+#endif
- 
- /**
-  * Maximum length of text representation of an IPv4 address.
-@@ -712,7 +718,7 @@ PJ_DECL(char*) pj_inet_ntoa(pj_in_addr inaddr);
-  *
-  * @return	nonzero if the address is valid, zero if not.
-  */
--PJ_DECL(int) pj_inet_aton(const pj_str_t *cp, struct pj_in_addr *inp);
-+PJ_DECL(int) pj_inet_aton(const pj_str_t *cp, pj_in_addr *inp);
- 
- /**
-  * This function converts an address in its standard text presentation form
-diff --git a/pjlib/src/pj/sock_bsd.c b/pjlib/src/pj/sock_bsd.c
-index e416991..940fce1 100644
---- a/pjlib/src/pj/sock_bsd.c
-+++ b/pjlib/src/pj/sock_bsd.c
-@@ -244,7 +244,7 @@ PJ_DEF(char*) pj_inet_ntoa(pj_in_addr inaddr)
-  * numbers-and-dots notation into binary data and stores it in the structure
-  * that inp points to. 
-  */
--PJ_DEF(int) pj_inet_aton(const pj_str_t *cp, struct pj_in_addr *inp)
-+PJ_DEF(int) pj_inet_aton(const pj_str_t *cp, pj_in_addr *inp)
- {
-     char tempaddr[PJ_INET_ADDRSTRLEN];
- 
-diff --git a/pjlib/src/pj/sock_symbian.cpp b/pjlib/src/pj/sock_symbian.cpp
-index 09239b0..e72bbda 100644
---- a/pjlib/src/pj/sock_symbian.cpp
-+++ b/pjlib/src/pj/sock_symbian.cpp
-@@ -299,7 +299,7 @@ PJ_DEF(char*) pj_inet_ntoa(pj_in_addr inaddr)
-  * numbers-and-dots notation into binary data and stores it in the structure
-  * that inp points to. 
-  */
--PJ_DEF(int) pj_inet_aton(const pj_str_t *cp, struct pj_in_addr *inp)
-+PJ_DEF(int) pj_inet_aton(const pj_str_t *cp, pj_in_addr *inp)
- {
-     enum { MAXIPLEN = PJ_INET_ADDRSTRLEN };
- 
-diff --git a/pjlib/src/pj/sock_uwp.cpp b/pjlib/src/pj/sock_uwp.cpp
-index 876c328..40250bf 100644
---- a/pjlib/src/pj/sock_uwp.cpp
-+++ b/pjlib/src/pj/sock_uwp.cpp
-@@ -933,7 +933,7 @@ PJ_DEF(char*) pj_inet_ntoa(pj_in_addr inaddr)
-  * numbers-and-dots notation into binary data and stores it in the structure
-  * that inp points to. 
-  */
--PJ_DEF(int) pj_inet_aton(const pj_str_t *cp, struct pj_in_addr *inp)
-+PJ_DEF(int) pj_inet_aton(const pj_str_t *cp, pj_in_addr *inp)
- {
-     char tempaddr[PJ_INET_ADDRSTRLEN];
- 
-diff --git a/pjsip/src/test/transport_test.c b/pjsip/src/test/transport_test.c
-index e5083d1..c429cc7 100644
---- a/pjsip/src/test/transport_test.c
-+++ b/pjsip/src/test/transport_test.c
-@@ -35,7 +35,7 @@ int generic_transport_test(pjsip_transport *tp)
- 
-     /* Check that local address name is valid. */
-     {
--	struct pj_in_addr addr;
-+	pj_in_addr addr;
- 
- 	if (pj_inet_pton(pj_AF_INET(), &tp->local_name.host,
- 			 &addr) == PJ_SUCCESS)
--- 
-2.7.4
-
diff --git a/third-party/pjproject/patches/0010-Make-sure-that-NOTIFY-tdata-is-set-before-sending-it_new-129fb323a66dd1fd16880fe5ba5e6a57.patch b/third-party/pjproject/patches/0010-Make-sure-that-NOTIFY-tdata-is-set-before-sending-it_new-129fb323a66dd1fd16880fe5ba5e6a57.patch
new file mode 100644
index 0000000000000000000000000000000000000000..009060a124d8bc2467a4bfc058ad6fc0b5e7eb80
--- /dev/null
+++ b/third-party/pjproject/patches/0010-Make-sure-that-NOTIFY-tdata-is-set-before-sending-it_new-129fb323a66dd1fd16880fe5ba5e6a57.patch
@@ -0,0 +1,46 @@
+From ac685b30c17be461b2bf5b46a772ed9742b8e985 Mon Sep 17 00:00:00 2001
+From: Riza Sulistyo <trengginas@users.noreply.github.com>
+Date: Thu, 9 Feb 2023 13:19:23 +0700
+Subject: [PATCH] Make sure that NOTIFY tdata is set before sending it.
+
+---
+ pjsip/src/pjsip-simple/evsub.c | 9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+diff --git a/pjsip/src/pjsip-simple/evsub.c b/pjsip/src/pjsip-simple/evsub.c
+index da0a9b416..68c1d3951 100644
+--- a/pjsip/src/pjsip-simple/evsub.c
++++ b/pjsip/src/pjsip-simple/evsub.c
+@@ -2216,23 +2216,26 @@ static void on_tsx_state_uas( pjsip_evsub *sub, pjsip_transaction *tsx,
+             }
+ 
+         }  else {
+             sub->state = old_state;
+             sub->state_str = old_state_str;
+         }
+ 
+         /* Send the pending NOTIFY sent by app from inside
+          * on_rx_refresh() callback.
+          */
+-        pj_assert(sub->pending_notify);
+-        status = pjsip_evsub_send_request(sub, sub->pending_notify);
+-        sub->pending_notify = NULL;
++        //pj_assert(sub->pending_notify);
++        /* Make sure that pending_notify is set. */
++        if (sub->pending_notify) {
++            status = pjsip_evsub_send_request(sub, sub->pending_notify);
++            sub->pending_notify = NULL;
++        }
+ 
+     } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method)==0) {
+ 
+         /* Handle authentication */
+         if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
+             (tsx->status_code==401 || tsx->status_code==407))
+         {
+             pjsip_tx_data *tdata;
+             pj_status_t status;
+             pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
+-- 
+2.39.1
+
diff --git a/third-party/pjproject/patches/0011-sip_inv_patch.patch b/third-party/pjproject/patches/0011-sip_inv_patch.patch
deleted file mode 100644
index 7f77c74792b54a8c9fe86ec66821e12035bb6f61..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0011-sip_inv_patch.patch
+++ /dev/null
@@ -1,39 +0,0 @@
-commit c3c1bf45cae2a35003aa16c267d59f97027f9c5e
-Author: Kevin Harwell <kharwell@digium.com>
-Date:   Thu Jun 11 11:11:13 2020 -0500
-
-    sip_inv - fix invite session ref count crash
-    
-    Ensure the session's ref count is only decremented under proper conditons.
-    
-    For more details see the following issue report:
-    https://github.com/pjsip/pjproject/issues/2443
-    
-    Patch supplied by sauwming
-
-diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
-index ca225015b..7c11b1c8e 100644
---- a/pjsip/src/pjsip-ua/sip_inv.c
-+++ b/pjsip/src/pjsip-ua/sip_inv.c
-@@ -323,9 +323,19 @@ static void inv_set_state(pjsip_inv_session *inv, pjsip_inv_state state,
- 	(*mod_inv.cb.on_state_changed)(inv, e);
-     pjsip_inv_dec_ref(inv);
- 
--    /* Only decrement when previous state is not already DISCONNECTED */
-+    /* The above callback may change the state, so we need to be careful here
-+     * and only decrement inv under the following conditions:
-+     * 1. If the state parameter is DISCONNECTED, and previous state is not
-+     *    already DISCONNECTED.
-+     *    This is to make sure that dec_ref() is not called more than once.
-+     * 2. If current state is PJSIP_INV_STATE_DISCONNECTED.
-+     *    This is to make sure that dec_ref() is not called if user restarts
-+     *    inv within the callback. Note that this check must be last since
-+     *    inv may have already been destroyed.
-+     */
-     if (state == PJSIP_INV_STATE_DISCONNECTED &&
--	prev_state != PJSIP_INV_STATE_DISCONNECTED) 
-+	prev_state != PJSIP_INV_STATE_DISCONNECTED &&
-+	inv->state == PJSIP_INV_STATE_DISCONNECTED) 
-     {
- 	pjsip_inv_dec_ref(inv);
-     }
diff --git a/third-party/pjproject/patches/0020-pjlib_cancel_timer_0.patch b/third-party/pjproject/patches/0020-pjlib_cancel_timer_0.patch
deleted file mode 100644
index 09f72d825eeb33562cab6f8f61fd8c5ac2d4aff7..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0020-pjlib_cancel_timer_0.patch
+++ /dev/null
@@ -1,39 +0,0 @@
-commit 40dd48d10911f4ff9b8dfbf16428fbc9acc434ba
-Author: Riza Sulistyo <trengginas@users.noreply.github.com>
-Date:   Thu Jul 9 17:47:24 2020 +0700
-
-    Modify timer_id check on cancel() (#2463)
-    
-    * modify timer_id check on cancel().
-    
-    * modification based on comments.
-
-diff --git a/pjlib/include/pj/timer.h b/pjlib/include/pj/timer.h
-index b738a6e76..4b76ab65d 100644
---- a/pjlib/include/pj/timer.h
-+++ b/pjlib/include/pj/timer.h
-@@ -120,7 +120,10 @@ typedef struct pj_timer_entry
- 
-     /** 
-      * Internal unique timer ID, which is assigned by the timer heap. 
--     * Application should not touch this ID.
-+     * Positive values indicate that the timer entry is running, 
-+     * while -1 means that it's not. Any other value may indicate that it 
-+     * hasn't been properly initialised or is in a bad state.
-+     * Application should not touch this ID. 
-      */
-     pj_timer_id_t _timer_id;
- 
-diff --git a/pjlib/src/pj/timer.c b/pjlib/src/pj/timer.c
-index 66516fce8..34966c481 100644
---- a/pjlib/src/pj/timer.c
-+++ b/pjlib/src/pj/timer.c
-@@ -535,7 +535,7 @@ static int cancel( pj_timer_heap_t *ht,
-     PJ_CHECK_STACK();
- 
-     // Check to see if the timer_id is out of range
--    if (entry->_timer_id < 0 || (pj_size_t)entry->_timer_id > ht->max_size) {
-+    if (entry->_timer_id < 1 || (pj_size_t)entry->_timer_id >= ht->max_size) {
- 	entry->_timer_id = -1;
-     	return 0;
-     }
diff --git a/third-party/pjproject/patches/0050-fix-race-parallel-build.patch b/third-party/pjproject/patches/0050-fix-race-parallel-build.patch
deleted file mode 100644
index 674baa2b38501dd969998a4e5b40f47f0ef692ee..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0050-fix-race-parallel-build.patch
+++ /dev/null
@@ -1,72 +0,0 @@
-From 78683646c8bc670ec730a42494e075f671a08e28 Mon Sep 17 00:00:00 2001
-From: Guido Falsi <mad@madpilot.net>
-Date: Mon, 11 May 2020 08:50:39 +0200
-Subject: [PATCH] Fix race condition in parallel builds (#2426)
-
-* Some targets residing in `OBJDIRS` are missing a dependency on that directory, which results in a race condition, causing build to fail sometimes due to the directory not existing when running parallel builds.
-
-* The `PJSUA_LIB` variable is not defined anywhere, resulting in an empty value, and no correct dependency on the pjsua shared library for `pjsua2`. The correct variable seems to be `PJSUA_LIB_LIB`, defined at the start of this same `Makefile`.
----
- build/rules.mak      | 12 ++++++------
- pjsip/build/Makefile |  2 +-
- 2 files changed, 7 insertions(+), 7 deletions(-)
-
-diff --git a/build/rules.mak b/build/rules.mak
-index 8fa98655e..912199c41 100644
---- a/build/rules.mak
-+++ b/build/rules.mak
-@@ -129,7 +129,7 @@ endif
- $(OBJDIR)/$(app).o: $(OBJDIRS) $(OBJS)
- 	$(CROSS_COMPILE)ld -r -o $@ $(OBJS)
- 
--$(OBJDIR)/$(app).ko: $(OBJDIR)/$(app).o
-+$(OBJDIR)/$(app).ko: $(OBJDIR)/$(app).o | $(OBJDIRS)
- 	@echo Creating kbuild Makefile...
- 	@echo "# Our module name:" > $(OBJDIR)/Makefile
- 	@echo 'obj-m += $(app).o' >> $(OBJDIR)/Makefile
-@@ -154,27 +154,27 @@ $(OBJDIR)/$(app).ko: $(OBJDIR)/$(app).o
- ../lib/$(app).ko: $(LIB) $(OBJDIR)/$(app).ko
- 	cp $(OBJDIR)/$(app).ko ../lib
- 
--$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.m
-+$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.m | $(OBJDIRS)
- 	$(CC) $($(APP)_CFLAGS) \
- 		$(CC_OUT)$(subst /,$(HOST_PSEP),$@) \
- 		$(subst /,$(HOST_PSEP),$<) 
- 
--$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.c
-+$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.c | $(OBJDIRS)
- 	$(CC) $($(APP)_CFLAGS) \
- 		$(CC_OUT)$(subst /,$(HOST_PSEP),$@) \
- 		$(subst /,$(HOST_PSEP),$<) 
- 
--$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.S
-+$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.S | $(OBJDIRS)
- 	$(CC) $($(APP)_CFLAGS) \
- 		$(CC_OUT)$(subst /,$(HOST_PSEP),$@) \
- 		$(subst /,$(HOST_PSEP),$<) 
- 
--$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.cpp
-+$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.cpp | $(OBJDIRS)
- 	$(CXX) $($(APP)_CXXFLAGS) \
- 		$(CC_OUT)$(subst /,$(HOST_PSEP),$@) \
- 		$(subst /,$(HOST_PSEP),$<)
- 
--$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.cc
-+$(OBJDIR)/%$(OBJEXT): $(SRCDIR)/%.cc | $(OBJDIRS)
- 	$(CXX) $($(APP)_CXXFLAGS) \
- 		$(CC_OUT)$(subst /,$(HOST_PSEP),$@) \
- 		$(subst /,$(HOST_PSEP),$<)
-diff --git a/pjsip/build/Makefile b/pjsip/build/Makefile
-index b85c7817a..20777909f 100644
---- a/pjsip/build/Makefile
-+++ b/pjsip/build/Makefile
-@@ -262,7 +262,7 @@ $(PJSUA_LIB_LIB) $(PJSUA_LIB_SONAME): $(PJSIP_LIB) $(PJSIP_SONAME) $(PJSIP_SIMPL
- 
- pjsua2-lib: $(PJSUA2_LIB_LIB)
- $(PJSUA2_LIB_SONAME): $(PJSUA2_LIB_LIB)
--$(PJSUA2_LIB_LIB) $(PJSUA2_LIB_SONAME): $(PJSUA_LIB) $(PSJUA_LIB_SONAME) $(PJSIP_LIB) $(PJSIP_SONAME) $(PJSIP_SIMPLE_LIB) $(PJSIP_SIMPLE_SONAME) $(PJSIP_UA_LIB) $(PJSIP_UA_SONAME)
-+$(PJSUA2_LIB_LIB) $(PJSUA2_LIB_SONAME): $(PJSUA_LIB_LIB) $(PJSUA_LIB_SONAME) $(PJSIP_LIB) $(PJSIP_SONAME) $(PJSIP_SIMPLE_LIB) $(PJSIP_SIMPLE_SONAME) $(PJSIP_UA_LIB) $(PJSIP_UA_SONAME)
- 	$(MAKE) -f $(RULES_MAK) APP=PJSUA2_LIB app=pjsua2-lib $(subst /,$(HOST_PSEP),$(LIBDIR)/$@)
- 
- pjsip-test: $(TEST_EXE)
diff --git a/third-party/pjproject/patches/0060-clone-sdp-for-sip-timer-refresh-invite.patch b/third-party/pjproject/patches/0060-clone-sdp-for-sip-timer-refresh-invite.patch
deleted file mode 100644
index f1f180ae7f122d7adefe422f009a607d0acdbb13..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0060-clone-sdp-for-sip-timer-refresh-invite.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-diff -ur source.orig/pjmedia/src/pjmedia/sdp_neg.c source/pjmedia/src/pjmedia/sdp_neg.c
---- source.orig/pjmedia/src/pjmedia/sdp_neg.c	2020-07-02 10:35:42.022459904 +0200
-+++ source/pjmedia/src/pjmedia/sdp_neg.c	2020-07-02 10:33:24.996316867 +0200
-@@ -906,7 +906,7 @@
-  * after receiving remote answer.
-  */
- static pj_status_t process_answer(pj_pool_t *pool,
--				  pjmedia_sdp_session *offer,
-+				  pjmedia_sdp_session *local_offer,
- 				  pjmedia_sdp_session *answer,
- 				  pj_bool_t allow_asym,
- 				  pjmedia_sdp_session **p_active)
-@@ -914,10 +914,14 @@
-     unsigned omi = 0; /* Offer media index */
-     unsigned ami = 0; /* Answer media index */
-     pj_bool_t has_active = PJ_FALSE;
-+    pjmedia_sdp_session *offer;
-     pj_status_t status;
- 
-     /* Check arguments. */
--    PJ_ASSERT_RETURN(pool && offer && answer && p_active, PJ_EINVAL);
-+    PJ_ASSERT_RETURN(pool && local_offer && answer && p_active, PJ_EINVAL);
-+
-+    /* Duplicate local offer SDP. */
-+    offer = pjmedia_sdp_session_clone(pool, local_offer);
- 
-     /* Check that media count match between offer and answer */
-     // Ticket #527, different media count is allowed for more interoperability,
diff --git a/third-party/pjproject/patches/0070-fix-incorrect-copying-when-creating-cancel.patch b/third-party/pjproject/patches/0070-fix-incorrect-copying-when-creating-cancel.patch
deleted file mode 100644
index 95725c1ecc382348ab40e8d516e432824b811841..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0070-fix-incorrect-copying-when-creating-cancel.patch
+++ /dev/null
@@ -1,37 +0,0 @@
-From ce18018cc17bef8f80c08686e3a7b28384ef3ba5 Mon Sep 17 00:00:00 2001
-From: sauwming <ming@teluu.com>
-Date: Mon, 12 Oct 2020 13:31:25 +0800
-Subject: [PATCH] Fix incorrect copying of destination info when creating
- CANCEL (#2546)
-
----
- pjsip/src/pjsip/sip_util.c | 10 +++++-----
- 1 file changed, 5 insertions(+), 5 deletions(-)
-
-diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c
-index d10a6fa30..a1bf878ea 100644
---- a/pjsip/src/pjsip/sip_util.c
-+++ b/pjsip/src/pjsip/sip_util.c
-@@ -779,14 +779,14 @@ PJ_DEF(pj_status_t) pjsip_endpt_create_cancel( pjsip_endpoint *endpt,
- 	    pjsip_hdr_clone(cancel_tdata->pool, req_tdata->saved_strict_route);
-     }
- 
--    /* Copy the destination host name from the original request */
--    pj_strdup(cancel_tdata->pool, &cancel_tdata->dest_info.name,
--	      &req_tdata->dest_info.name);
--
--    /* Finally copy the destination info from the original request */
-+    /* Copy the destination info from the original request */
-     pj_memcpy(&cancel_tdata->dest_info, &req_tdata->dest_info,
- 	      sizeof(req_tdata->dest_info));
- 
-+    /* Finally, copy the destination host name from the original request */
-+    pj_strdup(cancel_tdata->pool, &cancel_tdata->dest_info.name,
-+	      &req_tdata->dest_info.name);
-+
-     /* Done.
-      * Return the transmit buffer containing the CANCEL request.
-      */
--- 
-2.25.1
-
diff --git a/third-party/pjproject/patches/0080-fix-sdp-neg-modify-local-offer.patch b/third-party/pjproject/patches/0080-fix-sdp-neg-modify-local-offer.patch
deleted file mode 100644
index c27a4899145fcc6410ea9e5e248c3895147a8fbe..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0080-fix-sdp-neg-modify-local-offer.patch
+++ /dev/null
@@ -1,33 +0,0 @@
-diff --git a/pjmedia/src/pjmedia/sdp_neg.c b/pjmedia/src/pjmedia/sdp_neg.c
-index 3b85b4273..a14009662 100644
---- a/pjmedia/src/pjmedia/sdp_neg.c
-+++ b/pjmedia/src/pjmedia/sdp_neg.c
-@@ -304,7 +304,6 @@ PJ_DEF(pj_status_t) pjmedia_sdp_neg_modify_local_offer2(
- {
-     pjmedia_sdp_session *new_offer;
-     pjmedia_sdp_session *old_offer;
--    char media_used[PJMEDIA_MAX_SDP_MEDIA];
-     unsigned oi; /* old offer media index */
-     pj_status_t status;
- 
-@@ -323,8 +322,19 @@ PJ_DEF(pj_status_t) pjmedia_sdp_neg_modify_local_offer2(
-     /* Change state to STATE_LOCAL_OFFER */
-     neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER;
- 
-+    /* When there is no active local SDP in state PJMEDIA_SDP_NEG_STATE_DONE,
-+     * it means that the previous initial SDP nego must have been failed,
-+     * so we'll just set the local SDP offer here.
-+     */
-+    if (!neg->active_local_sdp) {
-+	neg->initial_sdp_tmp = NULL;
-+	neg->initial_sdp = pjmedia_sdp_session_clone(pool, local);
-+	neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local);
-+
-+	return PJ_SUCCESS;
-+    }
-+
-     /* Init vars */
--    pj_bzero(media_used, sizeof(media_used));
-     old_offer = neg->active_local_sdp;
-     new_offer = pjmedia_sdp_session_clone(pool, local);
- 
diff --git a/third-party/pjproject/patches/0090-Skip-unsupported-digest-algorithm-2408.patch b/third-party/pjproject/patches/0090-Skip-unsupported-digest-algorithm-2408.patch
deleted file mode 100644
index a2db2200e7c89f0708b1d71c30d6f133f98d4ad1..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0090-Skip-unsupported-digest-algorithm-2408.patch
+++ /dev/null
@@ -1,212 +0,0 @@
-From bdbeb7c4b2b11efc2e59f5dee7aa4360a2bc9fff Mon Sep 17 00:00:00 2001
-From: sauwming <ming@teluu.com>
-Date: Thu, 22 Apr 2021 14:03:28 +0800
-Subject: [PATCH 90/90] Skip unsupported digest algorithm (#2408)
-
-Co-authored-by: Nanang Izzuddin <nanang@teluu.com>
----
- pjsip/src/pjsip/sip_auth_client.c             | 32 +++++--
- tests/pjsua/scripts-sipp/uas-auth-two-algo.py |  7 ++
- .../pjsua/scripts-sipp/uas-auth-two-algo.xml  | 83 +++++++++++++++++++
- 3 files changed, 117 insertions(+), 5 deletions(-)
- create mode 100644 tests/pjsua/scripts-sipp/uas-auth-two-algo.py
- create mode 100644 tests/pjsua/scripts-sipp/uas-auth-two-algo.xml
-
-diff --git a/pjsip/src/pjsip/sip_auth_client.c b/pjsip/src/pjsip/sip_auth_client.c
-index 828b04db9..7eb2f5cd1 100644
---- a/pjsip/src/pjsip/sip_auth_client.c
-+++ b/pjsip/src/pjsip/sip_auth_client.c
-@@ -1042,7 +1042,7 @@ static pj_status_t process_auth( pj_pool_t *req_pool,
-     pjsip_hdr *hdr;
-     pj_status_t status;
- 
--    /* See if we have sent authorization header for this realm */
-+    /* See if we have sent authorization header for this realm (and scheme) */
-     hdr = tdata->msg->hdr.next;
-     while (hdr != &tdata->msg->hdr) {
- 	if ((hchal->type == PJSIP_H_WWW_AUTHENTICATE &&
-@@ -1052,7 +1052,8 @@ static pj_status_t process_auth( pj_pool_t *req_pool,
- 	{
- 	    sent_auth = (pjsip_authorization_hdr*) hdr;
- 	    if (pj_stricmp(&hchal->challenge.common.realm,
--			   &sent_auth->credential.common.realm )==0)
-+			   &sent_auth->credential.common.realm)==0 &&
-+		pj_stricmp(&hchal->scheme, &sent_auth->scheme)==0)
- 	    {
- 		/* If this authorization has empty response, remove it. */
- 		if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 &&
-@@ -1062,6 +1063,14 @@ static pj_status_t process_auth( pj_pool_t *req_pool,
- 		    hdr = hdr->next;
- 		    pj_list_erase(sent_auth);
- 		    continue;
-+		} else
-+		if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 &&
-+		    pj_stricmp(&sent_auth->credential.digest.algorithm,
-+		               &hchal->challenge.digest.algorithm)!=0)
-+		{
-+		    /* Same 'digest' scheme but different algo */
-+		    hdr = hdr->next;
-+		    continue;
- 		} else {
- 		    /* Found previous authorization attempt */
- 		    break;
-@@ -1155,9 +1164,10 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req(	pjsip_auth_clt_sess *sess,
- {
-     pjsip_tx_data *tdata;
-     const pjsip_hdr *hdr;
--    unsigned chal_cnt;
-+    unsigned chal_cnt, auth_cnt;
-     pjsip_via_hdr *via;
-     pj_status_t status;
-+    pj_status_t last_auth_err;
- 
-     PJ_ASSERT_RETURN(sess && rdata && old_request && new_request,
- 		     PJ_EINVAL);
-@@ -1178,6 +1188,8 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req(	pjsip_auth_clt_sess *sess,
-      */
-     hdr = rdata->msg_info.msg->hdr.next;
-     chal_cnt = 0;
-+    auth_cnt = 0;
-+    last_auth_err = PJSIP_EAUTHNOAUTH;
-     while (hdr != &rdata->msg_info.msg->hdr) {
- 	pjsip_cached_auth *cached_auth;
- 	const pjsip_www_authenticate_hdr *hchal;
-@@ -1222,8 +1234,13 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req(	pjsip_auth_clt_sess *sess,
- 	 */
- 	status = process_auth(tdata->pool, hchal, tdata->msg->line.req.uri,
- 			      tdata, sess, cached_auth, &hauth);
--	if (status != PJ_SUCCESS)
--	    return status;
-+	if (status != PJ_SUCCESS) {
-+	    last_auth_err = status;
-+
-+	    /* Process next header. */
-+	    hdr = hdr->next;
-+	    continue;
-+	}
- 
- 	if (pj_pool_get_used_size(cached_auth->pool) >
- 	    PJSIP_AUTH_CACHED_POOL_MAX_SIZE) 
-@@ -1236,12 +1253,17 @@ PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req(	pjsip_auth_clt_sess *sess,
- 
- 	/* Process next header. */
- 	hdr = hdr->next;
-+	auth_cnt++;
-     }
- 
-     /* Check if challenge is present */
-     if (chal_cnt == 0)
- 	return PJSIP_EAUTHNOCHAL;
- 
-+    /* Check if any authorization header has been created */
-+    if (auth_cnt == 0)
-+	return last_auth_err;
-+
-     /* Remove branch param in Via header. */
-     via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
-     via->branch_param.slen = 0;
-diff --git a/tests/pjsua/scripts-sipp/uas-auth-two-algo.py b/tests/pjsua/scripts-sipp/uas-auth-two-algo.py
-new file mode 100644
-index 000000000..c79c9f6d3
---- /dev/null
-+++ b/tests/pjsua/scripts-sipp/uas-auth-two-algo.py
-@@ -0,0 +1,7 @@
-+# $Id$
-+#
-+import inc_const as const
-+
-+PJSUA = ["--null-audio --max-calls=1 --id=sip:a@localhost --username=a --realm=* --registrar=$SIPP_URI"]
-+
-+PJSUA_EXPECTS = [[0, "registration success", ""]]
-diff --git a/tests/pjsua/scripts-sipp/uas-auth-two-algo.xml b/tests/pjsua/scripts-sipp/uas-auth-two-algo.xml
-new file mode 100644
-index 000000000..bd4871940
---- /dev/null
-+++ b/tests/pjsua/scripts-sipp/uas-auth-two-algo.xml
-@@ -0,0 +1,83 @@
-+<?xml version="1.0" encoding="ISO-8859-1" ?>
-+<!DOCTYPE scenario SYSTEM "sipp.dtd">
-+
-+<scenario name="Basic UAS responder">
-+  <recv request="REGISTER" crlf="true">
-+  </recv>
-+
-+  <send>
-+    <![CDATA[
-+      SIP/2.0 100 Trying
-+      [last_Via:];received=1.1.1.1;rport=1111
-+      [last_From:]
-+      [last_To:];tag=[call_number]
-+      [last_Call-ID:]
-+      [last_CSeq:]
-+      Content-Length: 0
-+    ]]>
-+  </send>
-+
-+  <send>
-+    <![CDATA[
-+      SIP/2.0 401 Unauthorized
-+      [last_Via:];received=1.1.1.1;rport=1111
-+      [last_From:]
-+      [last_To:];tag=[call_number]
-+      [last_Call-ID:]
-+      [last_CSeq:]
-+      WWW-Authenticate: Digest realm="sip.linphone.org", nonce="PARV4gAAAADgw3asAADW8zsi5BEAAAAA", opaque="+GNywA==", algorithm=SHA-256, qop="auth"
-+      WWW-Authenticate: Digest realm="sip.linphone.org", nonce="PARV4gAAAADgw3asAADW8zsi5BEAAAAA", opaque="+GNywA==", algorithm=MD5, qop="auth"
-+      WWW-Authenticate: Digest realm="sip.linphone.org", nonce="PARV4gAAAADgw3asAADW8zsi5BEAAAAA", opaque="+GNywA==", algorithm=MD2, qop="auth"
-+      Content-Length: 0
-+    ]]>
-+  </send>
-+
-+  <recv request="REGISTER" crlf="true">
-+    <action>
-+      <ereg regexp=".*"
-+            search_in="hdr"
-+	    header="Authorization:"
-+	    assign_to="have_auth" />
-+    </action>
-+  </recv>
-+
-+  <nop next="resp_okay" test="have_auth" />
-+  
-+  <send next="end">
-+    <![CDATA[
-+      SIP/2.0 403 no auth
-+      [last_Via:];received=1.1.1.1;rport=1111
-+      [last_From:]
-+      [last_To:];tag=[call_number]
-+      [last_Call-ID:]
-+      [last_CSeq:]
-+      [last_Contact:]
-+      Content-Length: 0
-+    ]]>
-+  </send>
-+
-+  <label id="resp_okay" />
-+  
-+  <send>
-+    <![CDATA[
-+      SIP/2.0 200 OK
-+      [last_Via:];received=1.1.1.1;rport=1111
-+      [last_From:]
-+      [last_To:];tag=[call_number]
-+      [last_Call-ID:]
-+      [last_CSeq:]
-+      [last_Contact:]
-+      Content-Length: 0
-+    ]]>
-+  </send>
-+
-+  <label id="end" />
-+
-+  <!-- definition of the response time repartition table (unit is ms)   -->
-+  <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
-+
-+  <!-- definition of the call length repartition table (unit is ms)     -->
-+  <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
-+
-+</scenario>
-+
--- 
-2.31.1
-
diff --git a/third-party/pjproject/patches/0100-fix-double-stun-free.patch b/third-party/pjproject/patches/0100-fix-double-stun-free.patch
deleted file mode 100644
index b1cfcfd512fdb1dc819ae3a9d984a2c151a27e01..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0100-fix-double-stun-free.patch
+++ /dev/null
@@ -1,82 +0,0 @@
-commit f0ff5817d0647bdecd1ec99488db9378e304cf83
-Author: sauwming <ming@teluu.com>
-Date:   Mon May 17 09:56:27 2021 +0800
-
-    Fix double free of stun session (#2709)
-
-diff --git a/pjnath/include/pjnath/stun_session.h b/pjnath/include/pjnath/stun_session.h
-index bee630ab4..afca06911 100644
---- a/pjnath/include/pjnath/stun_session.h
-+++ b/pjnath/include/pjnath/stun_session.h
-@@ -341,6 +341,7 @@ struct pj_stun_tx_data
-     pj_pool_t		*pool;		/**< Pool.			    */
-     pj_stun_session	*sess;		/**< The STUN session.		    */
-     pj_stun_msg		*msg;		/**< The STUN message.		    */
-+    pj_bool_t		 is_destroying; /**< Is destroying?		    */
- 
-     void		*token;		/**< The token.			    */
- 
-diff --git a/pjnath/src/pjnath/stun_session.c b/pjnath/src/pjnath/stun_session.c
-index f2b4f7058..d436b94bf 100644
---- a/pjnath/src/pjnath/stun_session.c
-+++ b/pjnath/src/pjnath/stun_session.c
-@@ -167,16 +167,27 @@ static void tdata_on_destroy(void *arg)
- {
-     pj_stun_tx_data *tdata = (pj_stun_tx_data*)arg;
- 
-+    if (tdata->grp_lock) {
-+	pj_grp_lock_dec_ref(tdata->sess->grp_lock);
-+    }
-+
-     pj_pool_safe_release(&tdata->pool);
- }
- 
- static void destroy_tdata(pj_stun_tx_data *tdata, pj_bool_t force)
- {
--    TRACE_((THIS_FILE, "tdata %p destroy request, force=%d, tsx=%p", tdata,
--	    force, tdata->client_tsx));
-+    TRACE_((THIS_FILE,
-+	    "tdata %p destroy request, force=%d, tsx=%p, destroying=%d",
-+	    tdata, force, tdata->client_tsx, tdata->is_destroying));
-+
-+    /* Just return if destroy has been requested before */
-+    if (tdata->is_destroying)
-+	return;
- 
-     /* STUN session may have been destroyed, except when tdata is cached. */
- 
-+    tdata->is_destroying = PJ_TRUE;
-+
-     if (tdata->res_timer.id != PJ_FALSE) {
- 	pj_timer_heap_cancel_if_active(tdata->sess->cfg->timer_heap,
- 				       &tdata->res_timer, PJ_FALSE);
-@@ -189,7 +200,6 @@ static void destroy_tdata(pj_stun_tx_data *tdata, pj_bool_t force)
- 	    pj_stun_client_tsx_set_data(tdata->client_tsx, NULL);
- 	}
- 	if (tdata->grp_lock) {
--	    pj_grp_lock_dec_ref(tdata->sess->grp_lock);
- 	    pj_grp_lock_dec_ref(tdata->grp_lock);
- 	} else {
- 	    tdata_on_destroy(tdata);
-@@ -200,11 +210,11 @@ static void destroy_tdata(pj_stun_tx_data *tdata, pj_bool_t force)
- 	    /* "Probably" this is to absorb retransmission */
- 	    pj_time_val delay = {0, 300};
- 	    pj_stun_client_tsx_schedule_destroy(tdata->client_tsx, &delay);
-+	    tdata->is_destroying = PJ_FALSE;
- 
- 	} else {
- 	    pj_list_erase(tdata);
- 	    if (tdata->grp_lock) {
--		pj_grp_lock_dec_ref(tdata->sess->grp_lock);
- 		pj_grp_lock_dec_ref(tdata->grp_lock);
- 	    } else {
- 		tdata_on_destroy(tdata);
-@@ -238,7 +248,7 @@ static void on_cache_timeout(pj_timer_heap_t *timer_heap,
-     sess = tdata->sess;
- 
-     pj_grp_lock_acquire(sess->grp_lock);
--    if (sess->is_destroying) {
-+    if (sess->is_destroying || tdata->is_destroying) {
- 	pj_grp_lock_release(sess->grp_lock);
- 	return;
-     }
diff --git a/third-party/pjproject/patches/0110-tls-parent-listener-destroyed.patch b/third-party/pjproject/patches/0110-tls-parent-listener-destroyed.patch
deleted file mode 100644
index 81781f2db13811d7f34fa2c8de4bfc2015c8ead3..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0110-tls-parent-listener-destroyed.patch
+++ /dev/null
@@ -1,166 +0,0 @@
-From bb92c97ea512aa0ef316c9b2335c7d57b84dfc9a Mon Sep 17 00:00:00 2001
-From: Nanang Izzuddin <nanang@teluu.com>
-Date: Wed, 16 Jun 2021 12:12:35 +0700
-Subject: [PATCH 1/2] - Avoid SSL socket parent/listener getting destroyed
- during handshake by increasing parent's reference count. - Add missing SSL
- socket close when the newly accepted SSL socket is discarded in SIP TLS
- transport.
-
----
- pjlib/src/pj/ssl_sock_imp_common.c  | 44 +++++++++++++++++++++--------
- pjsip/src/pjsip/sip_transport_tls.c | 23 ++++++++++++++-
- 2 files changed, 55 insertions(+), 12 deletions(-)
-
-diff --git a/pjlib/src/pj/ssl_sock_imp_common.c b/pjlib/src/pj/ssl_sock_imp_common.c
-index bc468bcb3..abec31805 100644
---- a/pjlib/src/pj/ssl_sock_imp_common.c
-+++ b/pjlib/src/pj/ssl_sock_imp_common.c
-@@ -224,6 +224,8 @@ static pj_bool_t on_handshake_complete(pj_ssl_sock_t *ssock,
- 
-     /* Accepting */
-     if (ssock->is_server) {
-+	pj_bool_t ret = PJ_TRUE;
-+
- 	if (status != PJ_SUCCESS) {
- 	    /* Handshake failed in accepting, destroy our self silently. */
- 
-@@ -241,6 +243,12 @@ static pj_bool_t on_handshake_complete(pj_ssl_sock_t *ssock,
- 		      status);
- 	    }
- 
-+	    /* Decrement ref count of parent */
-+	    if (ssock->parent->param.grp_lock) {
-+		pj_grp_lock_dec_ref(ssock->parent->param.grp_lock);
-+		ssock->parent = NULL;
-+	    }
-+
- 	    /* Originally, this is a workaround for ticket #985. However,
- 	     * a race condition may occur in multiple worker threads
- 	     * environment when we are destroying SSL objects while other
-@@ -284,23 +292,29 @@ static pj_bool_t on_handshake_complete(pj_ssl_sock_t *ssock,
- 
- 	    return PJ_FALSE;
- 	}
-+
- 	/* Notify application the newly accepted SSL socket */
- 	if (ssock->param.cb.on_accept_complete2) {
--	    pj_bool_t ret;
- 	    ret = (*ssock->param.cb.on_accept_complete2) 
- 		    (ssock->parent, ssock, (pj_sockaddr_t*)&ssock->rem_addr, 
- 		    pj_sockaddr_get_len((pj_sockaddr_t*)&ssock->rem_addr), 
- 		    status);
--	    if (ret == PJ_FALSE)
--		return PJ_FALSE;	
- 	} else if (ssock->param.cb.on_accept_complete) {
--	    pj_bool_t ret;
- 	    ret = (*ssock->param.cb.on_accept_complete)
- 		      (ssock->parent, ssock, (pj_sockaddr_t*)&ssock->rem_addr,
- 		       pj_sockaddr_get_len((pj_sockaddr_t*)&ssock->rem_addr));
--	    if (ret == PJ_FALSE)
--		return PJ_FALSE;
- 	}
-+
-+	/* Decrement ref count of parent and reset parent (we don't need it
-+	 * anymore, right?).
-+	 */
-+	if (ssock->parent->param.grp_lock) {
-+	    pj_grp_lock_dec_ref(ssock->parent->param.grp_lock);
-+	    ssock->parent = NULL;
-+	}
-+
-+	if (ret == PJ_FALSE)
-+	    return PJ_FALSE;
-     }
- 
-     /* Connecting */
-@@ -864,9 +878,13 @@ static pj_bool_t asock_on_accept_complete (pj_activesock_t *asock,
-     if (status != PJ_SUCCESS)
- 	goto on_return;
- 
-+    /* Set parent and add ref count (avoid parent destroy during handshake) */
-+    ssock->parent = ssock_parent;
-+    if (ssock->parent->param.grp_lock)
-+	pj_grp_lock_add_ref(ssock->parent->param.grp_lock);
-+
-     /* Update new SSL socket attributes */
-     ssock->sock = newsock;
--    ssock->parent = ssock_parent;
-     ssock->is_server = PJ_TRUE;
-     if (ssock_parent->cert) {
- 	status = pj_ssl_sock_set_certificate(ssock, ssock->pool, 
-@@ -913,16 +931,20 @@ static pj_bool_t asock_on_accept_complete (pj_activesock_t *asock,
-     ssock->asock_rbuf = (void**)pj_pool_calloc(ssock->pool, 
- 					       ssock->param.async_cnt,
- 					       sizeof(void*));
--    if (!ssock->asock_rbuf)
--        return PJ_ENOMEM;
-+    if (!ssock->asock_rbuf) {
-+		status = PJ_ENOMEM;
-+		goto on_return;
-+	}
- 
-     for (i = 0; i<ssock->param.async_cnt; ++i) {
--	ssock->asock_rbuf[i] = (void*) pj_pool_alloc(
-+		ssock->asock_rbuf[i] = (void*) pj_pool_alloc(
- 					    ssock->pool, 
- 					    ssock->param.read_buffer_size + 
- 					    sizeof(read_data_t*));
--        if (!ssock->asock_rbuf[i])
--            return PJ_ENOMEM;
-+		if (!ssock->asock_rbuf[i]) {
-+			status = PJ_ENOMEM;
-+			goto on_return;
-+		}
-     }
- 
-     /* Create active socket */
-diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c
-index 17b7ae3de..ce524d53f 100644
---- a/pjsip/src/pjsip/sip_transport_tls.c
-+++ b/pjsip/src/pjsip/sip_transport_tls.c
-@@ -1325,9 +1325,26 @@ static pj_bool_t on_accept_complete2(pj_ssl_sock_t *ssock,
-     PJ_UNUSED_ARG(src_addr_len);
- 
-     listener = (struct tls_listener*) pj_ssl_sock_get_user_data(ssock);
-+    if (!listener) {
-+	/* Listener already destroyed, e.g: after TCP accept but before SSL
-+	 * handshake is completed.
-+	 */
-+	if (new_ssock && accept_status == PJ_SUCCESS) {
-+	    /* Close the SSL socket if the accept op is successful */
-+	    PJ_LOG(4,(THIS_FILE,
-+		      "Incoming TLS connection from %s (sock=%d) is discarded "
-+		      "because listener is already destroyed",
-+		      pj_sockaddr_print(src_addr, addr, sizeof(addr), 3),
-+		      new_ssock));
-+
-+	    pj_ssl_sock_close(new_ssock);
-+	}
-+
-+	return PJ_FALSE;
-+    }
- 
-     if (accept_status != PJ_SUCCESS) {
--	if (listener && listener->tls_setting.on_accept_fail_cb) {
-+	if (listener->tls_setting.on_accept_fail_cb) {
- 	    pjsip_tls_on_accept_fail_param param;
- 	    pj_ssl_sock_info ssi;
- 
-@@ -1350,6 +1367,8 @@ static pj_bool_t on_accept_complete2(pj_ssl_sock_t *ssock,
-     PJ_ASSERT_RETURN(new_ssock, PJ_TRUE);
- 
-     if (!listener->is_registered) {
-+	pj_ssl_sock_close(new_ssock);
-+
- 	if (listener->tls_setting.on_accept_fail_cb) {
- 	    pjsip_tls_on_accept_fail_param param;
- 	    pj_bzero(&param, sizeof(param));
-@@ -1401,6 +1420,8 @@ static pj_bool_t on_accept_complete2(pj_ssl_sock_t *ssock,
- 			 ssl_info.grp_lock, &tls);
-     
-     if (status != PJ_SUCCESS) {
-+	pj_ssl_sock_close(new_ssock);
-+
- 	if (listener->tls_setting.on_accept_fail_cb) {
- 	    pjsip_tls_on_accept_fail_param param;
- 	    pj_bzero(&param, sizeof(param));
diff --git a/third-party/pjproject/patches/0111-ssl-premature-destroy.patch b/third-party/pjproject/patches/0111-ssl-premature-destroy.patch
deleted file mode 100644
index 9de2915b5a1081033affe0ba06ed16656118cdf7..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0111-ssl-premature-destroy.patch
+++ /dev/null
@@ -1,136 +0,0 @@
-From 68c69f516f95df1faa42e5647e9ce7cfdc41ac38 Mon Sep 17 00:00:00 2001
-From: Nanang Izzuddin <nanang@teluu.com>
-Date: Wed, 16 Jun 2021 12:15:29 +0700
-Subject: [PATCH 2/2] - Fix silly mistake: accepted active socket created
- without group lock in SSL socket. - Replace assertion with normal validation
- check of SSL socket instance in OpenSSL verification callback (verify_cb())
- to avoid crash, e.g: if somehow race condition with SSL socket destroy
- happens or OpenSSL application data index somehow gets corrupted.
-
----
- pjlib/src/pj/ssl_sock_imp_common.c |  3 +-
- pjlib/src/pj/ssl_sock_ossl.c       | 45 +++++++++++++++++++++++++-----
- 2 files changed, 40 insertions(+), 8 deletions(-)
-
-diff --git a/pjlib/src/pj/ssl_sock_imp_common.c b/pjlib/src/pj/ssl_sock_imp_common.c
-index bc468bcb3..c2b8a846b 100644
---- a/pjlib/src/pj/ssl_sock_imp_common.c
-+++ b/pjlib/src/pj/ssl_sock_imp_common.c
-@@ -927,6 +927,7 @@ static pj_bool_t asock_on_accept_complete (pj_activesock_t *asock,
- 
-     /* Create active socket */
-     pj_activesock_cfg_default(&asock_cfg);
-+    asock_cfg.grp_lock = ssock->param.grp_lock;
-     asock_cfg.async_cnt = ssock->param.async_cnt;
-     asock_cfg.concurrency = ssock->param.concurrency;
-     asock_cfg.whole_data = PJ_TRUE;
-@@ -942,7 +943,7 @@ static pj_bool_t asock_on_accept_complete (pj_activesock_t *asock,
- 	    goto on_return;
- 
- 	pj_grp_lock_add_ref(glock);
--	asock_cfg.grp_lock = ssock->param.grp_lock = glock;
-+	ssock->param.grp_lock = glock;
- 	pj_grp_lock_add_handler(ssock->param.grp_lock, ssock->pool, ssock,
- 				ssl_on_destroy);
-     }
-diff --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c
-index a95b339a5..56841f80a 100644
---- a/pjlib/src/pj/ssl_sock_ossl.c
-+++ b/pjlib/src/pj/ssl_sock_ossl.c
-@@ -327,7 +327,8 @@ static pj_status_t STATUS_FROM_SSL_ERR(char *action, pj_ssl_sock_t *ssock,
- 	ERROR_LOG("STATUS_FROM_SSL_ERR", err, ssock);
-     }
- 
--    ssock->last_err = err;
-+    if (ssock)
-+	ssock->last_err = err;
-     return GET_STATUS_FROM_SSL_ERR(err);
- }
- 
-@@ -344,7 +345,8 @@ static pj_status_t STATUS_FROM_SSL_ERR2(char *action, pj_ssl_sock_t *ssock,
-     /* Dig for more from OpenSSL error queue */
-     SSLLogErrors(action, ret, err, len, ssock);
- 
--    ssock->last_err = ssl_err;
-+    if (ssock)
-+	ssock->last_err = ssl_err;
-     return GET_STATUS_FROM_SSL_ERR(ssl_err);
- }
- 
-@@ -587,6 +589,13 @@ static pj_status_t init_openssl(void)
- 
-     /* Create OpenSSL application data index for SSL socket */
-     sslsock_idx = SSL_get_ex_new_index(0, "SSL socket", NULL, NULL, NULL);
-+	if (sslsock_idx == -1) {
-+		status = STATUS_FROM_SSL_ERR2("Init", NULL, -1, ERR_get_error(), 0);
-+		PJ_LOG(1,(THIS_FILE,
-+				  "Fatal error: failed to get application data index for "
-+				  "SSL socket"));
-+		return status;
-+	}
- 
-     return status;
- }
-@@ -614,21 +623,36 @@ static int password_cb(char *buf, int num, int rwflag, void *user_data)
- }
- 
- 
--/* SSL password callback. */
-+/* SSL certificate verification result callback.
-+ * Note that this callback seems to be always called from library worker
-+ * thread, e.g: active socket on_read_complete callback, which should have
-+ * already been equipped with race condition avoidance mechanism (should not
-+ * be destroyed while callback is being invoked).
-+ */
- static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
- {
--    pj_ssl_sock_t *ssock;
--    SSL *ossl_ssl;
-+    pj_ssl_sock_t *ssock = NULL;
-+    SSL *ossl_ssl = NULL;
-     int err;
- 
-     /* Get SSL instance */
-     ossl_ssl = X509_STORE_CTX_get_ex_data(x509_ctx, 
- 				    SSL_get_ex_data_X509_STORE_CTX_idx());
--    pj_assert(ossl_ssl);
-+    if (!ossl_ssl) {
-+	PJ_LOG(1,(THIS_FILE,
-+		  "SSL verification callback failed to get SSL instance"));
-+	goto on_return;
-+    }
- 
-     /* Get SSL socket instance */
-     ssock = SSL_get_ex_data(ossl_ssl, sslsock_idx);
--    pj_assert(ssock);
-+    if (!ssock) {
-+	/* SSL socket may have been destroyed */
-+	PJ_LOG(1,(THIS_FILE,
-+		  "SSL verification callback failed to get SSL socket "
-+		  "instance (sslsock_idx=%d).", sslsock_idx));
-+	goto on_return;
-+    }
- 
-     /* Store verification status */
-     err = X509_STORE_CTX_get_error(x509_ctx);
-@@ -706,6 +730,7 @@ static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
-     if (PJ_FALSE == ssock->param.verify_peer)
- 	preverify_ok = 1;
- 
-+on_return:
-     return preverify_ok;
- }
- 
-@@ -1213,6 +1238,12 @@ static void ssl_destroy(pj_ssl_sock_t *ssock)
- static void ssl_reset_sock_state(pj_ssl_sock_t *ssock)
- {
-     ossl_sock_t *ossock = (ossl_sock_t *)ssock;
-+
-+    /* Detach from SSL instance */
-+    if (ossock->ossl_ssl) {
-+	SSL_set_ex_data(ossock->ossl_ssl, sslsock_idx, NULL);
-+    }
-+
-     /**
-      * Avoid calling SSL_shutdown() if handshake wasn't completed.
-      * OpenSSL 1.0.2f complains if SSL_shutdown() is called during an
diff --git a/third-party/pjproject/patches/0120-pjmedia_sdp_attr_get_rtpmap-Strip-param-trailing-whi.patch b/third-party/pjproject/patches/0120-pjmedia_sdp_attr_get_rtpmap-Strip-param-trailing-whi.patch
deleted file mode 100644
index 1b1fcad177212b5d948f1b540733c308b2f89889..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0120-pjmedia_sdp_attr_get_rtpmap-Strip-param-trailing-whi.patch
+++ /dev/null
@@ -1,32 +0,0 @@
-From 2ae784030b0d9cf217c3d562af20e4967f19a3dc Mon Sep 17 00:00:00 2001
-From: George Joseph <gjoseph@sangoma.com>
-Date: Tue, 14 Sep 2021 10:47:29 -0600
-Subject: [PATCH] pjmedia_sdp_attr_get_rtpmap: Strip param trailing whitespace
-
-Use pj_scan_get() to parse the param part of rtpmap so
-trailing whitespace is automatically stripped.
-
-Fixes #2827
----
- pjmedia/src/pjmedia/sdp.c | 4 ++--
- 1 file changed, 2 insertions(+), 2 deletions(-)
-
-diff --git a/pjmedia/src/pjmedia/sdp.c b/pjmedia/src/pjmedia/sdp.c
-index 5d05a0d9c..3448749c9 100644
---- a/pjmedia/src/pjmedia/sdp.c
-+++ b/pjmedia/src/pjmedia/sdp.c
-@@ -313,9 +313,9 @@ PJ_DEF(pj_status_t) pjmedia_sdp_attr_get_rtpmap( const pjmedia_sdp_attr *attr,
- 
- 	/* Expecting either '/' or EOF */
- 	if (*scanner.curptr == '/') {
-+	    /* Skip the '/' */
- 	    pj_scan_get_char(&scanner);
--	    rtpmap->param.ptr = scanner.curptr;
--	    rtpmap->param.slen = scanner.end - scanner.curptr;
-+	    pj_scan_get(&scanner, &cs_token, &rtpmap->param);
- 	} else {
- 	    rtpmap->param.slen = 0;
- 	}
--- 
-2.31.1
-
diff --git a/third-party/pjproject/patches/0130-sip_inv-Additional-multipart-support-2919-2920.patch b/third-party/pjproject/patches/0130-sip_inv-Additional-multipart-support-2919-2920.patch
deleted file mode 100644
index 91feefb4bf7012f24b3fc73726d2cc61060d0b4c..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0130-sip_inv-Additional-multipart-support-2919-2920.patch
+++ /dev/null
@@ -1,661 +0,0 @@
-From 0ed41eb5fd0e4192e1b7dc374f819d17aef3e805 Mon Sep 17 00:00:00 2001
-From: George Joseph <gtjoseph@users.noreply.github.com>
-Date: Tue, 21 Dec 2021 19:32:22 -0700
-Subject: [PATCH] sip_inv:  Additional multipart support (#2919) (#2920)
-
----
- pjsip/include/pjsip-ua/sip_inv.h       | 108 ++++++++++-
- pjsip/src/pjsip-ua/sip_inv.c           | 240 ++++++++++++++++++++-----
- pjsip/src/test/inv_offer_answer_test.c | 103 ++++++++++-
- 3 files changed, 394 insertions(+), 57 deletions(-)
-
-diff --git a/pjsip/include/pjsip-ua/sip_inv.h b/pjsip/include/pjsip-ua/sip_inv.h
-index 14f2d23fa..c33551786 100644
---- a/pjsip/include/pjsip-ua/sip_inv.h
-+++ b/pjsip/include/pjsip-ua/sip_inv.h
-@@ -451,11 +451,11 @@ struct pjsip_inv_session
- 
- 
- /**
-- * This structure represents SDP information in a pjsip_rx_data. Application
-- * retrieve this information by calling #pjsip_rdata_get_sdp_info(). This
-+ * This structure represents SDP information in a pjsip_(rx|tx)_data. Application
-+ * retrieve this information by calling #pjsip_get_sdp_info(). This
-  * mechanism supports multipart message body.
-  */
--typedef struct pjsip_rdata_sdp_info
-+typedef struct pjsip_sdp_info
- {
-     /**
-      * Pointer and length of the text body in the incoming message. If
-@@ -475,7 +475,15 @@ typedef struct pjsip_rdata_sdp_info
-      */
-     pjmedia_sdp_session *sdp;
- 
--} pjsip_rdata_sdp_info;
-+} pjsip_sdp_info;
-+
-+/**
-+ * For backwards compatibility and completeness,
-+ * pjsip_rdata_sdp_info and pjsip_tdata_sdp_info
-+ * are typedef'd to pjsip_sdp_info.
-+ */
-+typedef pjsip_sdp_info pjsip_rdata_sdp_info;
-+typedef pjsip_sdp_info pjsip_tdata_sdp_info;
- 
- 
- /**
-@@ -1045,6 +1053,44 @@ PJ_DECL(pj_status_t) pjsip_create_sdp_body(pj_pool_t *pool,
- 					   pjmedia_sdp_session *sdp,
- 					   pjsip_msg_body **p_body);
- 
-+/**
-+ * This is a utility function to create a multipart body with the
-+ * SIP body as the first part.
-+ *
-+ * @param pool		Pool to allocate memory.
-+ * @param sdp		SDP session to be put in the SIP message body.
-+ * @param p_body	Pointer to receive SIP message body containing
-+ *			the SDP session.
-+ *
-+ * @return		PJ_SUCCESS on success.
-+ */
-+PJ_DECL(pj_status_t) pjsip_create_multipart_sdp_body( pj_pool_t *pool,
-+                                           pjmedia_sdp_session *sdp,
-+                                           pjsip_msg_body **p_body);
-+
-+/**
-+ * Retrieve SDP information from a message body. Application should
-+ * prefer to use this function rather than parsing the SDP manually since
-+ * this function supports multipart message body.
-+ *
-+ * This function will only parse the SDP once, the first time it is called
-+ * on the same message. Subsequent call on the same message will just pick
-+ * up the already parsed SDP from the message.
-+ *
-+ * @param pool               Pool to allocate memory.
-+ * @param body               The message body.
-+ * @param msg_media_type     From the rdata or tdata Content-Type header, if available.
-+ *                           If NULL, the content_type from the body will be used.
-+ * @param search_media_type  The media type to search for.
-+ *                           If NULL, "application/sdp" will be used.
-+ *
-+ * @return                   The SDP info.
-+ */
-+PJ_DECL(pjsip_sdp_info*) pjsip_get_sdp_info(pj_pool_t *pool,
-+					   pjsip_msg_body *body,
-+					   pjsip_media_type *msg_media_type,
-+					   const pjsip_media_type *search_media_type);
-+
- /**
-  * Retrieve SDP information from an incoming message. Application should
-  * prefer to use this function rather than parsing the SDP manually since
-@@ -1061,6 +1107,60 @@ PJ_DECL(pj_status_t) pjsip_create_sdp_body(pj_pool_t *pool,
- PJ_DECL(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata);
- 
- 
-+/**
-+ * Retrieve SDP information from an incoming message. Application should
-+ * prefer to use this function rather than parsing the SDP manually since
-+ * this function supports multipart message body.
-+ *
-+ * This function will only parse the SDP once, the first time it is called
-+ * on the same message. Subsequent call on the same message will just pick
-+ * up the already parsed SDP from the message.
-+ *
-+ * @param rdata               The incoming message.
-+ * @param search_media_type   The SDP media type to search for.
-+ *                            If NULL, "application/sdp" will be used.
-+ *
-+ * @return                    The SDP info.
-+ */
-+PJ_DECL(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info2(
-+					    pjsip_rx_data *rdata,
-+					    const pjsip_media_type *search_media_type);
-+
-+/**
-+ * Retrieve SDP information from an outgoing message. Application should
-+ * prefer to use this function rather than parsing the SDP manually since
-+ * this function supports multipart message body.
-+ *
-+ * This function will only parse the SDP once, the first time it is called
-+ * on the same message. Subsequent call on the same message will just pick
-+ * up the already parsed SDP from the message.
-+ *
-+ * @param tdata    The outgoing message.
-+ *
-+ * @return         The SDP info.
-+ */
-+PJ_DECL(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info(pjsip_tx_data *tdata);
-+
-+/**
-+ * Retrieve SDP information from an outgoing message. Application should
-+ * prefer to use this function rather than parsing the SDP manually since
-+ * this function supports multipart message body.
-+ *
-+ * This function will only parse the SDP once, the first time it is called
-+ * on the same message. Subsequent call on the same message will just pick
-+ * up the already parsed SDP from the message.
-+ *
-+ * @param tdata               The outgoing message.
-+ * @param search_media_type   The SDP media type to search for.
-+ *                            If NULL, "application/sdp" will be used.
-+ *
-+ * @return                    The SDP info.
-+ */
-+PJ_DECL(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info2(
-+					    pjsip_tx_data *tdata,
-+					    const pjsip_media_type *search_media_type);
-+
-+
- PJ_END_DECL
- 
- /**
-diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
-index ca225015b..b68ae0f16 100644
---- a/pjsip/src/pjsip-ua/sip_inv.c
-+++ b/pjsip/src/pjsip-ua/sip_inv.c
-@@ -118,6 +118,8 @@ static pj_status_t handle_timer_response(pjsip_inv_session *inv,
- static pj_bool_t inv_check_secure_dlg(pjsip_inv_session *inv,
- 				      pjsip_event *e);
- 
-+static int print_sdp(pjsip_msg_body *body, char *buf, pj_size_t len);
-+
- static void (*inv_state_handler[])( pjsip_inv_session *inv, pjsip_event *e) = 
- {
-     &inv_on_state_null,
-@@ -946,66 +948,170 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uac( pjsip_dialog *dlg,
-     return PJ_SUCCESS;
- }
- 
--PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata)
-+PJ_DEF(pjsip_sdp_info*) pjsip_get_sdp_info(pj_pool_t *pool,
-+                                           pjsip_msg_body *body,
-+                                           pjsip_media_type *msg_media_type,
-+                                           const pjsip_media_type *search_media_type)
- {
--    pjsip_rdata_sdp_info *sdp_info;
--    pjsip_msg_body *body = rdata->msg_info.msg->body;
--    pjsip_ctype_hdr *ctype_hdr = rdata->msg_info.ctype;
--    pjsip_media_type app_sdp;
-+    pjsip_sdp_info *sdp_info;
-+    pjsip_media_type search_type;
-+    pjsip_media_type multipart_mixed;
-+    pjsip_media_type multipart_alternative;
-+    pjsip_media_type *msg_type;
-+    pj_status_t status;
- 
--    sdp_info = (pjsip_rdata_sdp_info*)
--	       rdata->endpt_info.mod_data[mod_inv.mod.id];
--    if (sdp_info)
--	return sdp_info;
-+    sdp_info = PJ_POOL_ZALLOC_T(pool,
-+                                pjsip_sdp_info);
- 
--    sdp_info = PJ_POOL_ZALLOC_T(rdata->tp_info.pool,
--				pjsip_rdata_sdp_info);
-     PJ_ASSERT_RETURN(mod_inv.mod.id >= 0, sdp_info);
--    rdata->endpt_info.mod_data[mod_inv.mod.id] = sdp_info;
- 
--    pjsip_media_type_init2(&app_sdp, "application", "sdp");
-+    if (!body) {
-+        return sdp_info;
-+    }
- 
--    if (body && ctype_hdr &&
--	pj_stricmp(&ctype_hdr->media.type, &app_sdp.type)==0 &&
--	pj_stricmp(&ctype_hdr->media.subtype, &app_sdp.subtype)==0)
-+    if (msg_media_type) {
-+	msg_type = msg_media_type;
-+    } else {
-+	if (body->content_type.type.slen == 0) {
-+	    return sdp_info;
-+	}
-+	msg_type = &body->content_type;
-+    }
-+
-+    if (!search_media_type) {
-+        pjsip_media_type_init2(&search_type, "application", "sdp");
-+    } else {
-+        pj_memcpy(&search_type, search_media_type, sizeof(search_type));
-+    }
-+
-+    pjsip_media_type_init2(&multipart_mixed, "multipart", "mixed");
-+    pjsip_media_type_init2(&multipart_alternative, "multipart", "alternative");
-+
-+    if (pjsip_media_type_cmp(msg_type, &search_type, PJ_FALSE) == 0)
-     {
--	sdp_info->body.ptr = (char*)body->data;
--	sdp_info->body.slen = body->len;
--    } else if  (body && ctype_hdr &&
--	    	pj_stricmp2(&ctype_hdr->media.type, "multipart")==0 &&
--	    	(pj_stricmp2(&ctype_hdr->media.subtype, "mixed")==0 ||
--	    	 pj_stricmp2(&ctype_hdr->media.subtype, "alternative")==0))
-+	/*
-+	 * If the print_body function is print_sdp, we know that
-+	 * body->data is a pjmedia_sdp_session object and came from
-+	 * a tx_data.  If not, it's the text representation of the
-+	 * sdp from an rx_data.
-+	 */
-+        if (body->print_body == print_sdp) {
-+            sdp_info->sdp = body->data;
-+        } else {
-+            sdp_info->body.ptr = (char*)body->data;
-+            sdp_info->body.slen = body->len;
-+        }
-+    } else if (pjsip_media_type_cmp(&multipart_mixed, msg_type, PJ_FALSE) == 0 ||
-+	pjsip_media_type_cmp(&multipart_alternative, msg_type, PJ_FALSE) == 0)
-     {
--	pjsip_multipart_part *part;
-+        pjsip_multipart_part *part;
-+        part = pjsip_multipart_find_part(body, &search_type, NULL);
-+        if (part) {
-+            if (part->body->print_body == print_sdp) {
-+                sdp_info->sdp = part->body->data;
-+            } else {
-+                sdp_info->body.ptr = (char*)part->body->data;
-+                sdp_info->body.slen = part->body->len;
-+            }
-+        }
-+    }
- 
--	part = pjsip_multipart_find_part(body, &app_sdp, NULL);
--	if (part) {
--	    sdp_info->body.ptr = (char*)part->body->data;
--	    sdp_info->body.slen = part->body->len;
--	}
-+    /*
-+     * If the body was already a pjmedia_sdp_session, we can just
-+     * return it.  If not and there wasn't a text representation
-+     * of the sdp either, we can also just return.
-+     */
-+    if (sdp_info->sdp || !sdp_info->body.ptr) {
-+	return sdp_info;
-     }
- 
--    if (sdp_info->body.ptr) {
--	pj_status_t status;
--	status = pjmedia_sdp_parse(rdata->tp_info.pool,
--				   sdp_info->body.ptr,
--				   sdp_info->body.slen,
--				   &sdp_info->sdp);
--	if (status == PJ_SUCCESS)
--	    status = pjmedia_sdp_validate2(sdp_info->sdp, PJ_FALSE);
-+    /*
-+     * If the body was the text representation of teh SDP, we need
-+     * to parse it to create a pjmedia_sdp_session object.
-+     */
-+    status = pjmedia_sdp_parse(pool,
-+				sdp_info->body.ptr,
-+				sdp_info->body.slen,
-+				&sdp_info->sdp);
-+    if (status == PJ_SUCCESS)
-+	status = pjmedia_sdp_validate2(sdp_info->sdp, PJ_FALSE);
- 
--	if (status != PJ_SUCCESS) {
--	    sdp_info->sdp = NULL;
--	    PJ_PERROR(1,(THIS_FILE, status,
--			 "Error parsing/validating SDP body"));
--	}
-+    if (status != PJ_SUCCESS) {
-+	sdp_info->sdp = NULL;
-+	PJ_PERROR(1, (THIS_FILE, status,
-+	    "Error parsing/validating SDP body"));
-+    }
-+
-+    sdp_info->sdp_err = status;
-+
-+    return sdp_info;
-+}
- 
--	sdp_info->sdp_err = status;
-+PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info2(
-+                                            pjsip_rx_data *rdata,
-+                                            const pjsip_media_type *search_media_type)
-+{
-+    pjsip_media_type *msg_media_type = NULL;
-+    pjsip_rdata_sdp_info *sdp_info;
-+
-+    if (rdata->endpt_info.mod_data[mod_inv.mod.id]) {
-+	return (pjsip_rdata_sdp_info *)rdata->endpt_info.mod_data[mod_inv.mod.id];
-+    }
-+
-+    /*
-+     * rdata should have a Content-Type header at this point but we'll
-+     * make sure.
-+     */
-+    if (rdata->msg_info.ctype) {
-+	msg_media_type = &rdata->msg_info.ctype->media;
-+    }
-+    sdp_info = pjsip_get_sdp_info(rdata->tp_info.pool,
-+				   rdata->msg_info.msg->body,
-+				   msg_media_type,
-+				   search_media_type);
-+    rdata->endpt_info.mod_data[mod_inv.mod.id] = sdp_info;
-+
-+    return sdp_info;
-+}
-+
-+PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata)
-+{
-+    return pjsip_rdata_get_sdp_info2(rdata, NULL);
-+}
-+
-+PJ_DEF(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info2(
-+                                            pjsip_tx_data *tdata,
-+                                            const pjsip_media_type *search_media_type)
-+{
-+    pjsip_ctype_hdr *ctype_hdr = NULL;
-+    pjsip_media_type *msg_media_type = NULL;
-+    pjsip_tdata_sdp_info *sdp_info;
-+
-+    if (tdata->mod_data[mod_inv.mod.id]) {
-+	return (pjsip_tdata_sdp_info *)tdata->mod_data[mod_inv.mod.id];
-+    }
-+    /*
-+     * tdata won't usually have a Content-Type header at this point
-+     * but we'll check just the same,
-+     */
-+    ctype_hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTENT_TYPE, NULL);
-+    if (ctype_hdr) {
-+	msg_media_type = &ctype_hdr->media;
-     }
- 
-+    sdp_info = pjsip_get_sdp_info(tdata->pool,
-+				   tdata->msg->body,
-+				   msg_media_type,
-+				   search_media_type);
-+    tdata->mod_data[mod_inv.mod.id] = sdp_info;
-+
-     return sdp_info;
- }
- 
-+PJ_DEF(pjsip_tdata_sdp_info*) pjsip_tdata_get_sdp_info(pjsip_tx_data *tdata)
-+{
-+    return pjsip_tdata_get_sdp_info2(tdata, NULL);
-+}
- 
- /*
-  * Verify incoming INVITE request.
-@@ -1730,13 +1836,55 @@ PJ_DEF(pj_status_t) pjsip_create_sdp_body( pj_pool_t *pool,
-     return PJ_SUCCESS;
- }
- 
-+static pjsip_multipart_part* create_sdp_part(pj_pool_t *pool, pjmedia_sdp_session *sdp)
-+{
-+    pjsip_multipart_part *sdp_part;
-+    pjsip_media_type media_type;
-+
-+    pjsip_media_type_init2(&media_type, "application", "sdp");
-+
-+    sdp_part = pjsip_multipart_create_part(pool);
-+    PJ_ASSERT_RETURN(sdp_part != NULL, NULL);
-+
-+    sdp_part->body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
-+    PJ_ASSERT_RETURN(sdp_part->body != NULL, NULL);
-+
-+    pjsip_media_type_cp(pool, &sdp_part->body->content_type, &media_type);
-+
-+    sdp_part->body->data = sdp;
-+    sdp_part->body->clone_data = clone_sdp;
-+    sdp_part->body->print_body = print_sdp;
-+
-+    return sdp_part;
-+}
-+
-+PJ_DEF(pj_status_t) pjsip_create_multipart_sdp_body(pj_pool_t *pool,
-+						     pjmedia_sdp_session *sdp,
-+						     pjsip_msg_body **p_body)
-+{
-+    pjsip_media_type media_type;
-+    pjsip_msg_body *multipart;
-+    pjsip_multipart_part *sdp_part;
-+
-+    pjsip_media_type_init2(&media_type, "multipart", "mixed");
-+    multipart = pjsip_multipart_create(pool, &media_type, NULL);
-+    PJ_ASSERT_RETURN(multipart != NULL, PJ_ENOMEM);
-+
-+    sdp_part = create_sdp_part(pool, sdp);
-+    PJ_ASSERT_RETURN(sdp_part != NULL, PJ_ENOMEM);
-+    pjsip_multipart_add_part(pool, multipart, sdp_part);
-+    *p_body = multipart;
-+
-+    return PJ_SUCCESS;
-+}
-+
- static pjsip_msg_body *create_sdp_body(pj_pool_t *pool,
- 				       const pjmedia_sdp_session *c_sdp)
- {
-     pjsip_msg_body *body;
-     pj_status_t status;
- 
--    status = pjsip_create_sdp_body(pool, 
-+    status = pjsip_create_sdp_body(pool,
- 				   pjmedia_sdp_session_clone(pool, c_sdp),
- 				   &body);
- 
-@@ -2059,6 +2207,7 @@ static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
- 	       )
- 	   )
- 	{
-+	    pjsip_sdp_info *tdata_sdp_info;
- 	    const pjmedia_sdp_session *reoffer_sdp = NULL;
- 
- 	    PJ_LOG(4,(inv->obj_name, "Received %s response "
-@@ -2067,14 +2216,15 @@ static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
- 		      (st_code/10==18? "early" : "final" )));
- 
- 	    /* Retrieve original SDP offer from INVITE request */
--	    reoffer_sdp = (const pjmedia_sdp_session*) 
--			  tsx->last_tx->msg->body->data;
-+	    tdata_sdp_info = pjsip_tdata_get_sdp_info(tsx->last_tx);
-+	    reoffer_sdp = tdata_sdp_info->sdp;
- 
- 	    /* Feed the original offer to negotiator */
- 	    status = pjmedia_sdp_neg_modify_local_offer2(inv->pool_prov, 
- 							 inv->neg,
-                                                          inv->sdp_neg_flags,
- 						         reoffer_sdp);
-+
- 	    if (status != PJ_SUCCESS) {
- 		PJ_LOG(1,(inv->obj_name, "Error updating local offer for "
- 			  "forked 2xx/18x response (err=%d)", status));
-diff --git a/pjsip/src/test/inv_offer_answer_test.c b/pjsip/src/test/inv_offer_answer_test.c
-index ad5fcd409..9cdd2654b 100644
---- a/pjsip/src/test/inv_offer_answer_test.c
-+++ b/pjsip/src/test/inv_offer_answer_test.c
-@@ -137,6 +137,7 @@ typedef struct inv_test_param_t
-     pj_bool_t	need_established;
-     unsigned	count;
-     oa_t	oa[4];
-+    pj_bool_t	multipart_body;
- } inv_test_param_t;
- 
- typedef struct inv_test_t
-@@ -257,6 +258,17 @@ static void on_media_update(pjsip_inv_session *inv_ses,
- 	    }
- 	}
- 
-+	/* Special handling for standard offer/answer */
-+	if (inv_test.param.count == 1 &&
-+	    inv_test.param.oa[0] == OFFERER_UAC &&
-+	    inv_test.param.need_established)
-+	{
-+	    jobs[job_cnt].type = ESTABLISH_CALL;
-+	    jobs[job_cnt].who = PJSIP_ROLE_UAS;
-+	    job_cnt++;
-+	    TRACE_((THIS_FILE, "      C+++"));
-+	}
-+
- 	pj_assert(job_cnt <= PJ_ARRAY_SIZE(jobs));
-     }
- }
-@@ -333,6 +345,15 @@ static pj_bool_t on_rx_request(pjsip_rx_data *rdata)
- 					  NULL, &tdata);
- 	pj_assert(status == PJ_SUCCESS);
- 
-+	/* Use multipart body, if configured */
-+	if (sdp && inv_test.param.multipart_body) {
-+	     status = pjsip_create_multipart_sdp_body(
-+				tdata->pool,
-+				pjmedia_sdp_session_clone(tdata->pool, sdp),
-+				&tdata->msg->body);
-+	}
-+	pj_assert(status == PJ_SUCCESS);
-+
- 	status = pjsip_inv_send_msg(inv_test.uas, tdata);
- 	pj_assert(status == PJ_SUCCESS);
- 
-@@ -426,6 +447,7 @@ static int perform_test(inv_test_param_t *param)
- 	sdp = NULL;
- 
-     status = pjsip_inv_create_uac(dlg, sdp, inv_test.param.inv_option, &inv_test.uac);
-+    //inv_test.uac->create_multipart = param->multipart_body;
-     PJ_ASSERT_RETURN(status==PJ_SUCCESS, -20);
- 
-     TRACE_((THIS_FILE, "    Sending INVITE %s offer", (sdp ? "with" : "without")));
-@@ -436,8 +458,17 @@ static int perform_test(inv_test_param_t *param)
-     status = pjsip_inv_invite(inv_test.uac, &tdata);
-     PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);
- 
-+    /* Use multipart body, if configured */
-+    if (sdp && param->multipart_body) {
-+	 status = pjsip_create_multipart_sdp_body(
-+			    tdata->pool,
-+			    pjmedia_sdp_session_clone(tdata->pool, sdp),
-+			    &tdata->msg->body);
-+    }
-+    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -40);
-+
-     status = pjsip_inv_send_msg(inv_test.uac, tdata);
--    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);
-+    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -50);
- 
-     /*
-      * Wait until test completes
-@@ -525,13 +556,14 @@ static inv_test_param_t test_params[] =
-     200/INVITE (answer)	<--
-     ACK    		-->
-  */
--#if 0
-+#if 1
-     {
- 	"Standard INVITE with offer",
- 	0,
- 	PJ_TRUE,
- 	1,
--	{ OFFERER_UAC }
-+	{ OFFERER_UAC },
-+	PJ_FALSE
-     },
- 
-     {
-@@ -539,7 +571,25 @@ static inv_test_param_t test_params[] =
- 	PJSIP_INV_REQUIRE_100REL,
- 	PJ_TRUE,
- 	1,
--	{ OFFERER_UAC }
-+	{ OFFERER_UAC },
-+	PJ_FALSE
-+    },
-+    {
-+	"Standard INVITE with offer, with Multipart",
-+	0,
-+	PJ_TRUE,
-+	1,
-+	{ OFFERER_UAC },
-+	PJ_TRUE
-+    },
-+
-+    {
-+	"Standard INVITE with offer, with 100rel, with Multipart",
-+	PJSIP_INV_REQUIRE_100REL,
-+	PJ_TRUE,
-+	1,
-+	{ OFFERER_UAC },
-+	PJ_TRUE
-     },
- #endif
- 
-@@ -555,7 +605,8 @@ static inv_test_param_t test_params[] =
- 	0,
- 	PJ_TRUE,
- 	1,
--	{ OFFERER_UAS }
-+	{ OFFERER_UAS },
-+	PJ_FALSE
-     },
- 
-     {
-@@ -563,7 +614,25 @@ static inv_test_param_t test_params[] =
- 	PJSIP_INV_REQUIRE_100REL,
- 	PJ_TRUE,
- 	1,
--	{ OFFERER_UAS }
-+	{ OFFERER_UAS },
-+	PJ_FALSE
-+    },
-+    {
-+	"INVITE with no offer, with Multipart",
-+	0,
-+	PJ_TRUE,
-+	1,
-+	{ OFFERER_UAS },
-+	PJ_TRUE
-+    },
-+
-+    {
-+	"INVITE with no offer, with 100rel, with Multipart",
-+	PJSIP_INV_REQUIRE_100REL,
-+	PJ_TRUE,
-+	1,
-+	{ OFFERER_UAS },
-+	PJ_TRUE
-     },
- #endif
- 
-@@ -584,14 +653,24 @@ static inv_test_param_t test_params[] =
- 	0,
- 	PJ_TRUE,
- 	2,
--	{ OFFERER_UAC, OFFERER_UAC }
-+	{ OFFERER_UAC, OFFERER_UAC },
-+	PJ_FALSE
-+    },
-+    {
-+	"INVITE and UPDATE by UAC, with Multipart",
-+	0,
-+	PJ_TRUE,
-+	2,
-+	{ OFFERER_UAC, OFFERER_UAC },
-+	PJ_TRUE
-     },
-     {
- 	"INVITE and UPDATE by UAC, with 100rel",
- 	PJSIP_INV_REQUIRE_100REL,
- 	PJ_TRUE,
- 	2,
--	{ OFFERER_UAC, OFFERER_UAC }
-+	{ OFFERER_UAC, OFFERER_UAC },
-+	PJ_FALSE
-     },
- #endif
- 
-@@ -617,6 +696,14 @@ static inv_test_param_t test_params[] =
- 	4,
- 	{ OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS }
-     },
-+    {
-+	"INVITE and many UPDATE by UAC and UAS, with Multipart",
-+	0,
-+	PJ_TRUE,
-+	4,
-+	{ OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS },
-+	PJ_TRUE
-+    },
- 
- };
- 
--- 
-2.33.1
-
diff --git a/third-party/pjproject/patches/0140-Fix-incorrect-unescaping-of-tokens-during-parsing-29.patch b/third-party/pjproject/patches/0140-Fix-incorrect-unescaping-of-tokens-during-parsing-29.patch
deleted file mode 100644
index 22df638dbe7c42510881016de3bd6a6ca61c6e5c..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0140-Fix-incorrect-unescaping-of-tokens-during-parsing-29.patch
+++ /dev/null
@@ -1,123 +0,0 @@
-From 3faf1d2b4da553bbaee04f9a13a5d084b381e5fb Mon Sep 17 00:00:00 2001
-From: sauwming <ming@teluu.com>
-Date: Tue, 4 Jan 2022 15:28:49 +0800
-Subject: [PATCH] Fix incorrect unescaping of tokens during parsing (#2933)
-
----
- pjsip/src/pjsip/sip_parser.c | 29 +++++++++++++++++++++++++----
- pjsip/src/test/msg_test.c    |  6 +++---
- 2 files changed, 28 insertions(+), 7 deletions(-)
-
-diff --git a/pjsip/src/pjsip/sip_parser.c b/pjsip/src/pjsip/sip_parser.c
-index c2add3299..b9a7c6a5c 100644
---- a/pjsip/src/pjsip/sip_parser.c
-+++ b/pjsip/src/pjsip/sip_parser.c
-@@ -378,17 +378,23 @@ static pj_status_t init_parser()
-     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
-     pj_cis_add_str( &pconst.pjsip_TOKEN_SPEC, TOKEN);
- 
-+    /* Token is allowed to have '%' so we do not need this. */
-+    /*
-     status = pj_cis_dup(&pconst.pjsip_TOKEN_SPEC_ESC, &pconst.pjsip_TOKEN_SPEC);
-     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
-     pj_cis_del_str(&pconst.pjsip_TOKEN_SPEC_ESC, "%");
-+    */
- 
-     status = pj_cis_dup(&pconst.pjsip_VIA_PARAM_SPEC, &pconst.pjsip_TOKEN_SPEC);
-     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
-     pj_cis_add_str(&pconst.pjsip_VIA_PARAM_SPEC, "[:]");
- 
-+    /* Token is allowed to have '%' */
-+    /*
-     status = pj_cis_dup(&pconst.pjsip_VIA_PARAM_SPEC_ESC, &pconst.pjsip_TOKEN_SPEC_ESC);
-     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
-     pj_cis_add_str(&pconst.pjsip_VIA_PARAM_SPEC_ESC, "[:]");
-+    */
- 
-     status = pj_cis_dup(&pconst.pjsip_HOST_SPEC, &pconst.pjsip_ALNUM_SPEC);
-     PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
-@@ -1210,7 +1216,11 @@ static void parse_param_imp( pj_scanner *scanner, pj_pool_t *pool,
- 			     unsigned option)
- {
-     /* pname */
--    parser_get_and_unescape(scanner, pool, spec, esc_spec, pname);
-+    if (!esc_spec) {
-+    	pj_scan_get(scanner, spec, pname);
-+    } else {
-+	parser_get_and_unescape(scanner, pool, spec, esc_spec, pname);
-+    }
- 
-     /* init pvalue */
-     pvalue->ptr = NULL;
-@@ -1240,7 +1250,12 @@ static void parse_param_imp( pj_scanner *scanner, pj_pool_t *pool,
- 		// pj_scan_get_until_ch(scanner, ']', pvalue);
- 		// pj_scan_get_char(scanner);
- 	    } else if(pj_cis_match(spec, *scanner->curptr)) {
--		parser_get_and_unescape(scanner, pool, spec, esc_spec, pvalue);
-+	    	if (!esc_spec) {
-+    		    pj_scan_get(scanner, spec, pvalue);
-+    		} else {
-+		    parser_get_and_unescape(scanner, pool, spec, esc_spec,
-+		    			    pvalue);
-+		}
- 	    }
- 	}
-     }
-@@ -1252,7 +1267,10 @@ PJ_DEF(void) pjsip_parse_param_imp(pj_scanner *scanner, pj_pool_t *pool,
- 			     	   unsigned option)
- {
-     parse_param_imp(scanner, pool, pname, pvalue, &pconst.pjsip_TOKEN_SPEC,
--		    &pconst.pjsip_TOKEN_SPEC_ESC, option);
-+		    // Token does not need to be unescaped.
-+		    // Refer to PR #2933.
-+		    // &pconst.pjsip_TOKEN_SPEC_ESC,
-+		    NULL, option);
- }
- 
- 
-@@ -2168,7 +2186,10 @@ static void int_parse_via_param( pjsip_via_hdr *hdr, pj_scanner *scanner,
- 	pj_scan_get_char(scanner);
- 	parse_param_imp(scanner, pool, &pname, &pvalue,
- 			&pconst.pjsip_VIA_PARAM_SPEC,
--			&pconst.pjsip_VIA_PARAM_SPEC_ESC,
-+		    	// Token does not need to be unescaped.
-+		     	// Refer to PR #2933.
-+		    	// &pconst.pjsip_VIA_PARAM_SPEC_ESC,
-+			NULL,
- 			0);
- 
- 	if (!parser_stricmp(pname, pconst.pjsip_BRANCH_STR) && pvalue.slen) {
-diff --git a/pjsip/src/test/msg_test.c b/pjsip/src/test/msg_test.c
-index c511e1cf6..24e3d405d 100644
---- a/pjsip/src/test/msg_test.c
-+++ b/pjsip/src/test/msg_test.c
-@@ -953,7 +953,7 @@ static int hdr_test_subject_utf(pjsip_hdr *h);
- 
- 
- #define GENERIC_PARAM	     "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3"
--#define GENERIC_PARAM_PARSED "p0=a;p1=\"ab:;cd\";p2=ab:cd;p3"
-+#define GENERIC_PARAM_PARSED "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3"
- #define PARAM_CHAR	     "][/:&+$"
- #define SIMPLE_ADDR_SPEC     "sip:host"
- #define ADDR_SPEC	     SIMPLE_ADDR_SPEC ";"PARAM_CHAR"="PARAM_CHAR ";p1=\";\""
-@@ -1401,7 +1401,7 @@ static int generic_param_test(pjsip_param *param_head)
-     param = param->next;
-     if (pj_strcmp2(&param->name, "p2"))
- 	return -956;
--    if (pj_strcmp2(&param->value, "ab:cd"))
-+    if (pj_strcmp2(&param->value, "ab%3acd"))
- 	return -957;
- 
-     param = param->next;
-@@ -1621,7 +1621,7 @@ static int hdr_test_content_type(pjsip_hdr *h)
-     prm = prm->next;
-     if (prm == &hdr->media.param) return -1960;
-     if (pj_strcmp2(&prm->name, "p2")) return -1961;
--    if (pj_strcmp2(&prm->value, "ab:cd")) return -1962;
-+    if (pj_strcmp2(&prm->value, "ab%3acd")) return -1962;
- 
-     prm = prm->next;
-     if (prm == &hdr->media.param) return -1970;
--- 
-2.32.0
-
diff --git a/third-party/pjproject/patches/0150-Create-generic-pjsip_hdr_find-functions.patch b/third-party/pjproject/patches/0150-Create-generic-pjsip_hdr_find-functions.patch
deleted file mode 100644
index 6ddb3469cc233132501cb0598b64829f997f34b6..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0150-Create-generic-pjsip_hdr_find-functions.patch
+++ /dev/null
@@ -1,176 +0,0 @@
-From 7e3dfd8a15fd0f98dbf0e04d2d7a5bded90ee401 Mon Sep 17 00:00:00 2001
-From: George Joseph <gjoseph@sangoma.com>
-Date: Tue, 11 Jan 2022 09:27:23 -0700
-Subject: [PATCH] Create generic pjsip_hdr_find functions
-
-pjsip_msg_find_hdr(), pjsip_msg_find_hdr_by_name(), and
-pjsip_msg_find_hdr_by_names() require a pjsip_msg to be passed in
-so if you need to search a header list that's not in a pjsip_msg,
-you have to do it yourself.  This commit adds generic versions of
-those 3 functions that take in the actual header list head instead
-of a pjsip_msg so if you need to search a list of headers in
-something like a pjsip_multipart_part, you can do so easily.
----
- pjsip/include/pjsip/sip_msg.h | 53 +++++++++++++++++++++++++++++++++++
- pjsip/src/pjsip/sip_msg.c     | 51 +++++++++++++++++++++++----------
- 2 files changed, 89 insertions(+), 15 deletions(-)
-
-diff --git a/pjsip/include/pjsip/sip_msg.h b/pjsip/include/pjsip/sip_msg.h
-index 4c9100d39..e3502e94e 100644
---- a/pjsip/include/pjsip/sip_msg.h
-+++ b/pjsip/include/pjsip/sip_msg.h
-@@ -362,6 +362,59 @@ PJ_DECL(void*) pjsip_hdr_shallow_clone( pj_pool_t *pool, const void *hdr );
-  */
- PJ_DECL(int) pjsip_hdr_print_on( void *hdr, char *buf, pj_size_t len);
- 
-+/**
-+ * Find a header in a header list by the header type.
-+ *
-+ * @param hdr_list  The "head" of the header list.
-+ * @param type      The header type to find.
-+ * @param start     The first header field where the search should begin.
-+ *                  If NULL is specified, then the search will begin from the
-+ *                  first header, otherwise the search will begin at the
-+ *                  specified header.
-+ *
-+ * @return          The header field, or NULL if no header with the specified
-+ *                  type is found.
-+ */
-+PJ_DECL(void*)  pjsip_hdr_find( const void *hdr_list,
-+				pjsip_hdr_e type,
-+				const void *start);
-+
-+/**
-+ * Find a header in a header list by its name.
-+ *
-+ * @param hdr_list  The "head" of the header list.
-+ * @param name      The header name to find.
-+ * @param start     The first header field where the search should begin.
-+ *                  If NULL is specified, then the search will begin from the
-+ *                  first header, otherwise the search will begin at the
-+ *                  specified header.
-+ *
-+ * @return          The header field, or NULL if no header with the specified
-+ *                  type is found.
-+ */
-+PJ_DECL(void*)  pjsip_hdr_find_by_name( const void *hdr_list,
-+					const pj_str_t *name,
-+					const void *start);
-+
-+/**
-+ * Find a header in a header list by its name and short name version.
-+ *
-+ * @param hdr_list  The "head" of the header list.
-+ * @param name      The header name to find.
-+ * @param sname     The short name version of the header name.
-+ * @param start     The first header field where the search should begin.
-+ *                  If NULL is specified, then the search will begin from the
-+ *                  first header, otherwise the search will begin at the
-+ *                  specified header.
-+ *
-+ * @return	    The header field, or NULL if no header with the specified
-+ *		    type is found.
-+ */
-+PJ_DECL(void*)  pjsip_hdr_find_by_names( const void *hdr_list,
-+					 const pj_str_t *name,
-+					 const pj_str_t *sname,
-+					 const void *start);
-+
- /**
-  * @}
-  */
-diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c
-index 6ba3054da..2a6a96af0 100644
---- a/pjsip/src/pjsip/sip_msg.c
-+++ b/pjsip/src/pjsip/sip_msg.c
-@@ -356,13 +356,13 @@ PJ_DEF(pjsip_msg*) pjsip_msg_clone( pj_pool_t *pool, const pjsip_msg *src)
-     return dst;
- }
- 
--PJ_DEF(void*)  pjsip_msg_find_hdr( const pjsip_msg *msg, 
--				   pjsip_hdr_e hdr_type, const void *start)
-+PJ_DEF(void*)  pjsip_hdr_find( const void *hdr_list,
-+			       pjsip_hdr_e hdr_type, const void *start)
- {
--    const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=&msg->hdr;
-+    const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=hdr_list;
- 
-     if (hdr == NULL) {
--	hdr = msg->hdr.next;
-+	hdr = end->next;
-     }
-     for (; hdr!=end; hdr = hdr->next) {
- 	if (hdr->type == hdr_type)
-@@ -371,14 +371,14 @@ PJ_DEF(void*)  pjsip_msg_find_hdr( const pjsip_msg *msg,
-     return NULL;
- }
- 
--PJ_DEF(void*)  pjsip_msg_find_hdr_by_name( const pjsip_msg *msg, 
--					   const pj_str_t *name, 
--					   const void *start)
-+PJ_DEF(void*)  pjsip_hdr_find_by_name( const void *hdr_list,
-+				       const pj_str_t *name,
-+				       const void *start)
- {
--    const pjsip_hdr *hdr=(const pjsip_hdr*)start, *end=&msg->hdr;
-+    const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=hdr_list;
- 
-     if (hdr == NULL) {
--	hdr = msg->hdr.next;
-+	hdr = end->next;
-     }
-     for (; hdr!=end; hdr = hdr->next) {
- 	if (pj_stricmp(&hdr->name, name) == 0)
-@@ -387,15 +387,15 @@ PJ_DEF(void*)  pjsip_msg_find_hdr_by_name( const pjsip_msg *msg,
-     return NULL;
- }
- 
--PJ_DEF(void*)  pjsip_msg_find_hdr_by_names( const pjsip_msg *msg, 
--					    const pj_str_t *name, 
--					    const pj_str_t *sname,
--					    const void *start)
-+PJ_DEF(void*)  pjsip_hdr_find_by_names( const void *hdr_list,
-+					const pj_str_t *name,
-+					const pj_str_t *sname,
-+					const void *start)
- {
--    const pjsip_hdr *hdr=(const pjsip_hdr*)start, *end=&msg->hdr;
-+    const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=hdr_list;
- 
-     if (hdr == NULL) {
--	hdr = msg->hdr.next;
-+	hdr = end->next;
-     }
-     for (; hdr!=end; hdr = hdr->next) {
- 	if (pj_stricmp(&hdr->name, name) == 0)
-@@ -406,6 +406,27 @@ PJ_DEF(void*)  pjsip_msg_find_hdr_by_names( const pjsip_msg *msg,
-     return NULL;
- }
- 
-+PJ_DEF(void*)  pjsip_msg_find_hdr( const pjsip_msg *msg,
-+				   pjsip_hdr_e hdr_type, const void *start)
-+{
-+    return pjsip_hdr_find(&msg->hdr, hdr_type, start);
-+}
-+
-+PJ_DEF(void*)  pjsip_msg_find_hdr_by_name( const pjsip_msg *msg,
-+					   const pj_str_t *name,
-+					   const void *start)
-+{
-+    return pjsip_hdr_find_by_name(&msg->hdr, name, start);
-+}
-+
-+PJ_DEF(void*)  pjsip_msg_find_hdr_by_names( const pjsip_msg *msg,
-+					    const pj_str_t *name,
-+					    const pj_str_t *sname,
-+					    const void *start)
-+{
-+    return pjsip_hdr_find_by_names(&msg->hdr, name, sname, start);
-+}
-+
- PJ_DEF(void*) pjsip_msg_find_remove_hdr( pjsip_msg *msg, 
- 				         pjsip_hdr_e hdr_type, void *start)
- {
--- 
-2.34.1
-
diff --git a/third-party/pjproject/patches/0160-Additional-multipart-improvements.patch b/third-party/pjproject/patches/0160-Additional-multipart-improvements.patch
deleted file mode 100644
index 373f9b8abe03dd86e22a22790218bb932b0f424d..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0160-Additional-multipart-improvements.patch
+++ /dev/null
@@ -1,644 +0,0 @@
-From b7ecff22e77887626fd8e8608c4dd73bc7b7366f Mon Sep 17 00:00:00 2001
-From: George Joseph <gjoseph@sangoma.com>
-Date: Tue, 18 Jan 2022 06:14:31 -0700
-Subject: [PATCH] Additional multipart improvements
-
-Added the following APIs:
-pjsip_multipart_find_part_by_header()
-pjsip_multipart_find_part_by_header_str()
-pjsip_multipart_find_part_by_cid_str()
-pjsip_multipart_find_part_by_cid_uri()
----
- pjsip/include/pjsip/sip_multipart.h |  83 ++++++++++
- pjsip/src/pjsip/sip_multipart.c     | 223 +++++++++++++++++++++++++++
- pjsip/src/test/multipart_test.c     | 225 +++++++++++++++++++++++++++-
- 3 files changed, 530 insertions(+), 1 deletion(-)
-
-diff --git a/pjsip/include/pjsip/sip_multipart.h b/pjsip/include/pjsip/sip_multipart.h
-index 1c05767c5..c6b82b0b4 100644
---- a/pjsip/include/pjsip/sip_multipart.h
-+++ b/pjsip/include/pjsip/sip_multipart.h
-@@ -153,6 +153,89 @@ pjsip_multipart_find_part( const pjsip_msg_body *mp,
- 			   const pjsip_media_type *content_type,
- 			   const pjsip_multipart_part *start);
- 
-+/**
-+ * Find a body inside multipart bodies which has a header matching the
-+ * supplied one. Most useful for finding a part with a specific Content-ID.
-+ *
-+ * @param pool		Memory pool to use for temp space.
-+ * @param mp		The multipart body.
-+ * @param search_hdr	Header to search for.
-+ * @param start		If specified, the search will begin at
-+ * 			start->next part. Otherwise it will begin at
-+ * 			the first part in the multipart bodies.
-+ *
-+ * @return		The first part which has a header matching the
-+ * 			specified one, or NULL if not found.
-+ */
-+PJ_DECL(pjsip_multipart_part*)
-+pjsip_multipart_find_part_by_header(pj_pool_t *pool,
-+				    const pjsip_msg_body *mp,
-+				    void *search_hdr,
-+				    const pjsip_multipart_part *start);
-+
-+/**
-+ * Find a body inside multipart bodies which has a header matching the
-+ * supplied name and value. Most useful for finding a part with a specific
-+ * Content-ID.
-+ *
-+ * @param pool		Memory pool to use for temp space.
-+ * @param mp		The multipart body.
-+ * @param hdr_name	Header name to search for.
-+ * @param hdr_value	Header value search for.
-+ * @param start		If specified, the search will begin at
-+ * 			start->next part. Otherwise it will begin at
-+ * 			the first part in the multipart bodies.
-+ *
-+ * @return		The first part which has a header matching the
-+ * 			specified one, or NULL if not found.
-+ */
-+PJ_DECL(pjsip_multipart_part*)
-+pjsip_multipart_find_part_by_header_str(pj_pool_t *pool,
-+				    const pjsip_msg_body *mp,
-+				    const pj_str_t *hdr_name,
-+				    const pj_str_t *hdr_value,
-+				    const pjsip_multipart_part *start);
-+
-+
-+
-+/**
-+ * Find a body inside multipart bodies which has a Content-ID value matching the
-+ * supplied "cid" URI in pj_str form.  The "cid:" scheme will be assumed if the
-+ * URL doesn't start with it.  Enclosing angle brackets will also be handled
-+ * correctly if they exist.
-+ *
-+ * @see RFC2392 Content-ID and Message-ID Uniform Resource Locators
-+ *
-+ * @param pool	Memory pool to use for temp space.
-+ * @param mp	The multipart body.
-+ * @param cid	The "cid" URI to search for in pj_str form.
-+ *
-+ * @return		The first part which has a Content-ID header matching the
-+ * 			specified "cid" URI. or NULL if not found.
-+ */
-+PJ_DECL(pjsip_multipart_part*)
-+pjsip_multipart_find_part_by_cid_str(pj_pool_t *pool,
-+				 const pjsip_msg_body *mp,
-+				 pj_str_t *cid);
-+
-+/**
-+ * Find a body inside multipart bodies which has a Content-ID value matching the
-+ * supplied "cid" URI.
-+ *
-+ * @see RFC2392 Content-ID and Message-ID Uniform Resource Locators
-+ *
-+ * @param pool	Memory pool to use for temp space.
-+ * @param mp	The multipart body.
-+ * @param cid	The "cid" URI to search for.
-+ *
-+ * @return		The first part which had a Content-ID header matching the
-+ * 			specified "cid" URI. or NULL if not found.
-+ */
-+PJ_DECL(pjsip_multipart_part*)
-+pjsip_multipart_find_part_by_cid_uri(pj_pool_t *pool,
-+				 const pjsip_msg_body *mp,
-+				 pjsip_other_uri *cid_uri);
-+
- /**
-  * Parse multipart message.
-  *
-diff --git a/pjsip/src/pjsip/sip_multipart.c b/pjsip/src/pjsip/sip_multipart.c
-index e7d722d2e..9d8be55b0 100644
---- a/pjsip/src/pjsip/sip_multipart.c
-+++ b/pjsip/src/pjsip/sip_multipart.c
-@@ -19,6 +19,7 @@
- #include <pjsip/sip_multipart.h>
- #include <pjsip/sip_parser.h>
- #include <pjlib-util/scanner.h>
-+#include <pjlib-util/string.h>
- #include <pj/assert.h>
- #include <pj/ctype.h>
- #include <pj/errno.h>
-@@ -416,6 +417,220 @@ pjsip_multipart_find_part( const pjsip_msg_body *mp,
-     return NULL;
- }
- 
-+/*
-+ * Find a body inside multipart bodies which has the header and value.
-+ */
-+PJ_DEF(pjsip_multipart_part*)
-+pjsip_multipart_find_part_by_header_str(pj_pool_t *pool,
-+				    const pjsip_msg_body *mp,
-+				    const pj_str_t *hdr_name,
-+				    const pj_str_t *hdr_value,
-+				    const pjsip_multipart_part *start)
-+{
-+    struct multipart_data *m_data;
-+    pjsip_multipart_part *part;
-+    pjsip_hdr *found_hdr;
-+    pj_str_t found_hdr_str;
-+    pj_str_t found_hdr_value;
-+    pj_size_t expected_hdr_slen;
-+    pj_size_t buf_size;
-+    int hdr_name_len;
-+#define REASONABLE_PADDING 32
-+#define SEPARATOR_LEN 2
-+    /* Must specify mandatory params */
-+    PJ_ASSERT_RETURN(mp && hdr_name && hdr_value, NULL);
-+
-+    /* mp must really point to an actual multipart msg body */
-+    PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
-+
-+    /*
-+     * We'll need to "print" each header we find to test it but
-+     * allocating a buffer of PJSIP_MAX_URL_SIZE is overkill.
-+     * Instead, we'll allocate one large enough to hold the search
-+     * header name, the ": " separator, the search hdr value, and
-+     * the NULL terminator.  If we can't print the found header
-+     * into that buffer then it can't be a match.
-+     *
-+     * Some header print functions such as generic_int require enough
-+     * space to print the maximum possible header length so we'll
-+     * add a reasonable amount to the print buffer size.
-+     */
-+    expected_hdr_slen = hdr_name->slen + SEPARATOR_LEN + hdr_value->slen;
-+    buf_size = expected_hdr_slen + REASONABLE_PADDING;
-+    found_hdr_str.ptr = pj_pool_alloc(pool, buf_size);
-+    found_hdr_str.slen = 0;
-+    hdr_name_len = hdr_name->slen + SEPARATOR_LEN;
-+
-+    m_data = (struct multipart_data*)mp->data;
-+
-+    if (start)
-+	part = start->next;
-+    else
-+	part = m_data->part_head.next;
-+
-+    while (part != &m_data->part_head) {
-+	found_hdr = NULL;
-+	while ((found_hdr = pjsip_hdr_find_by_name(&part->hdr, hdr_name,
-+	    (found_hdr ? found_hdr->next : NULL))) != NULL) {
-+
-+	    found_hdr_str.slen = pjsip_hdr_print_on((void*) found_hdr, found_hdr_str.ptr, buf_size);
-+	    /*
-+	     * If the buffer was too small (slen = -1) or the result wasn't
-+	     * the same length as the search header, it can't be a match.
-+	     */
-+	    if (found_hdr_str.slen != expected_hdr_slen) {
-+		continue;
-+	    }
-+	    /*
-+	     * Set the value overlay to start at the found header value...
-+	     */
-+	    found_hdr_value.ptr = found_hdr_str.ptr + hdr_name_len;
-+	    found_hdr_value.slen = found_hdr_str.slen - hdr_name_len;
-+	    /* ...and compare it to the supplied header value. */
-+	    if (pj_strcmp(hdr_value, &found_hdr_value) == 0) {
-+		return part;
-+	    }
-+	}
-+	part = part->next;
-+    }
-+    return NULL;
-+#undef SEPARATOR_LEN
-+#undef REASONABLE_PADDING
-+}
-+
-+PJ_DEF(pjsip_multipart_part*)
-+pjsip_multipart_find_part_by_header(pj_pool_t *pool,
-+				    const pjsip_msg_body *mp,
-+				    void *search_for,
-+				    const pjsip_multipart_part *start)
-+{
-+    struct multipart_data *m_data;
-+    pjsip_hdr *search_hdr = search_for;
-+    pj_str_t search_buf;
-+
-+    /* Must specify mandatory params */
-+    PJ_ASSERT_RETURN(mp && search_hdr, NULL);
-+
-+    /* mp must really point to an actual multipart msg body */
-+    PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
-+
-+    /*
-+     * Unfortunately, there isn't enough information to determine
-+     * the maximum printed size of search_hdr at this point so we
-+     * have to allocate a reasonable max.
-+     */
-+    search_buf.ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
-+    search_buf.slen = pjsip_hdr_print_on(search_hdr, search_buf.ptr, PJSIP_MAX_URL_SIZE - 1);
-+    if (search_buf.slen <= 0) {
-+	return NULL;
-+    }
-+    /*
-+     * Set the header value to start after the header name plus the ":", then
-+     * strip leading and trailing whitespace.
-+     */
-+    search_buf.ptr += (search_hdr->name.slen + 1);
-+    search_buf.slen -= (search_hdr->name.slen + 1);
-+    pj_strtrim(&search_buf);
-+
-+    return pjsip_multipart_find_part_by_header_str(pool, mp, &search_hdr->name, &search_buf, start);
-+}
-+
-+/*
-+ * Convert a Content-ID URI to it's corresponding header value.
-+ * RFC2392 says...
-+ * A "cid" URL is converted to the corresponding Content-ID message
-+ * header by removing the "cid:" prefix, converting the % encoded
-+ * character(s) to their equivalent US-ASCII characters, and enclosing
-+ * the remaining parts with an angle bracket pair, "<" and ">".
-+ *
-+ * This implementation will accept URIs with or without the "cid:"
-+ * scheme and optional angle brackets.
-+ */
-+static pj_str_t cid_uri_to_hdr_value(pj_pool_t *pool, pj_str_t *cid_uri)
-+{
-+    pj_size_t cid_len = pj_strlen(cid_uri);
-+    pj_size_t alloc_len = cid_len + 2 /* for the leading and trailing angle brackets */;
-+    pj_str_t uri_overlay;
-+    pj_str_t cid_hdr;
-+    pj_str_t hdr_overlay;
-+
-+    pj_strassign(&uri_overlay, cid_uri);
-+    /* If the URI is already enclosed in angle brackets, remove them. */
-+    if (uri_overlay.ptr[0] == '<') {
-+	uri_overlay.ptr++;
-+	uri_overlay.slen -= 2;
-+    }
-+    /* If the URI starts with the "cid:" scheme, skip over it. */
-+    if (pj_strncmp2(&uri_overlay, "cid:", 4) == 0) {
-+	uri_overlay.ptr += 4;
-+	uri_overlay.slen -= 4;
-+    }
-+    /* Start building */
-+    cid_hdr.ptr = pj_pool_alloc(pool, alloc_len);
-+    cid_hdr.ptr[0] = '<';
-+    cid_hdr.slen = 1;
-+    hdr_overlay.ptr = cid_hdr.ptr + 1;
-+    hdr_overlay.slen = 0;
-+    pj_strcpy_unescape(&hdr_overlay, &uri_overlay);
-+    cid_hdr.slen += hdr_overlay.slen;
-+    cid_hdr.ptr[cid_hdr.slen] = '>';
-+    cid_hdr.slen++;
-+
-+    return cid_hdr;
-+}
-+
-+PJ_DEF(pjsip_multipart_part*)
-+pjsip_multipart_find_part_by_cid_str(pj_pool_t *pool,
-+				 const pjsip_msg_body *mp,
-+				 pj_str_t *cid)
-+{
-+    struct multipart_data *m_data;
-+    pjsip_multipart_part *part;
-+    pjsip_generic_string_hdr *found_hdr;
-+    pj_str_t found_hdr_value;
-+    static pj_str_t hdr_name = { "Content-ID", 10};
-+    pj_str_t hdr_value;
-+
-+    PJ_ASSERT_RETURN(pool && mp && cid && (pj_strlen(cid) > 0), NULL);
-+
-+    hdr_value = cid_uri_to_hdr_value(pool, cid);
-+    if (pj_strlen(&hdr_value) == 0) {
-+	return NULL;
-+    }
-+
-+    m_data = (struct multipart_data*)mp->data;
-+    part = m_data->part_head.next;
-+
-+    while (part != &m_data->part_head) {
-+	found_hdr = NULL;
-+	while ((found_hdr = pjsip_hdr_find_by_name(&part->hdr, &hdr_name,
-+	    (found_hdr ? found_hdr->next : NULL))) != NULL) {
-+	    if (pj_strcmp(&hdr_value, &found_hdr->hvalue) == 0) {
-+		return part;
-+	    }
-+	}
-+	part = part->next;
-+    }
-+    return NULL;
-+}
-+
-+PJ_DEF(pjsip_multipart_part*)
-+pjsip_multipart_find_part_by_cid_uri(pj_pool_t *pool,
-+				 const pjsip_msg_body *mp,
-+				 pjsip_other_uri *cid_uri)
-+{
-+    PJ_ASSERT_RETURN(pool && mp && cid_uri, NULL);
-+
-+    if (pj_strcmp2(&cid_uri->scheme, "cid") != 0) {
-+	return NULL;
-+    }
-+    /*
-+     * We only need to pass the URI content so we
-+     * can do that directly.
-+     */
-+    return pjsip_multipart_find_part_by_cid_str(pool, mp, &cid_uri->content);
-+}
-+
- /* Parse a multipart part. "pct" is parent content-type  */
- static pjsip_multipart_part *parse_multipart_part(pj_pool_t *pool,
- 						  char *start,
-@@ -584,6 +799,7 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_parse(pj_pool_t *pool,
- 		(int)boundary.slen, boundary.ptr));
-     }
- 
-+
-     /* Build the delimiter:
-      *   delimiter = "--" boundary
-      */
-@@ -630,6 +846,8 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_parse(pj_pool_t *pool,
- 	if (*curptr=='\r') ++curptr;
- 	if (*curptr!='\n') {
- 	    /* Expecting a newline here */
-+	    PJ_LOG(2, (THIS_FILE, "Failed to find newline"));
-+
- 	    return NULL;
- 	}
- 	++curptr;
-@@ -645,6 +863,7 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_parse(pj_pool_t *pool,
- 	    curptr = pj_strstr(&subbody, &delim);
- 	    if (!curptr) {
- 		/* We're really expecting end delimiter to be found. */
-+		PJ_LOG(2, (THIS_FILE, "Failed to find end-delimiter"));
- 		return NULL;
- 	    }
- 	}
-@@ -670,9 +889,13 @@ PJ_DEF(pjsip_msg_body*) pjsip_multipart_parse(pj_pool_t *pool,
- 	part = parse_multipart_part(pool, start_body, end_body - start_body,
- 				    ctype);
- 	if (part) {
-+	    TRACE_((THIS_FILE, "Adding part"));
- 	    pjsip_multipart_add_part(pool, body, part);
-+	} else {
-+	    PJ_LOG(2, (THIS_FILE, "Failed to add part"));
- 	}
-     }
-+    TRACE_((THIS_FILE, "pjsip_multipart_parse finished: %p", body));
- 
-     return body;
- }
-diff --git a/pjsip/src/test/multipart_test.c b/pjsip/src/test/multipart_test.c
-index 4f16e68bf..97267a290 100644
---- a/pjsip/src/test/multipart_test.c
-+++ b/pjsip/src/test/multipart_test.c
-@@ -28,6 +28,7 @@
- typedef pj_status_t (*verify_ptr)(pj_pool_t*,pjsip_msg_body*);
- 
- static pj_status_t verify1(pj_pool_t *pool, pjsip_msg_body *body);
-+static pj_status_t verify2(pj_pool_t *pool, pjsip_msg_body *body);
- 
- static struct test_t
- {
-@@ -68,7 +69,41 @@ static struct test_t
- 		"This is epilogue, which should be ignored too",
- 
- 		&verify1
-+	},
-+	{
-+		/* Content-type */
-+		"multipart", "mixed", "12345",
-+
-+		/* Body: */
-+		"This is the prolog, which should be ignored.\r\n"
-+		"--12345\r\n"
-+		"Content-Type: text/plain\r\n"
-+		"Content-ID: <header1@example.org>\r\n"
-+		"Content-ID: <\"header1\"@example.org>\r\n"
-+		"Content-Length: 13\r\n"
-+		"\r\n"
-+		"has header1\r\n"
-+		"--12345 \t\r\n"
-+		"Content-Type: application/pidf+xml\r\n"
-+		"Content-ID: <my header2@example.org>\r\n"
-+		"Content-ID: <my\xffheader2@example.org>\r\n"
-+		"Content-Length: 13\r\n"
-+		"\r\n"
-+		"has header2\r\n"
-+		"--12345\r\n"
-+		"Content-Type: text/plain\r\n"
-+		"Content-ID: <my header3@example.org>\r\n"
-+		"Content-ID: <header1@example.org>\r\n"
-+		"Content-ID: <my header4@example.org>\r\n"
-+		"Content-Length: 13\r\n"
-+		"\r\n"
-+		"has header4\r\n"
-+		"--12345--\r\n"
-+		"This is epilogue, which should be ignored too",
-+
-+		&verify2
- 	}
-+
- };
- 
- static void init_media_type(pjsip_media_type *mt,
-@@ -87,6 +122,192 @@ static void init_media_type(pjsip_media_type *mt,
-     }
- }
- 
-+static int verify_hdr(pj_pool_t *pool, pjsip_msg_body *multipart_body,
-+    void *hdr, char *part_body)
-+{
-+    pjsip_media_type mt;
-+    pjsip_multipart_part *part;
-+    pj_str_t the_body;
-+
-+
-+    part = pjsip_multipart_find_part_by_header(pool, multipart_body, hdr, NULL);
-+    if (!part) {
-+	return -1;
-+    }
-+
-+    the_body.ptr = (char*)part->body->data;
-+    the_body.slen = part->body->len;
-+
-+    if (pj_strcmp2(&the_body, part_body) != 0) {
-+	return -2;
-+    }
-+
-+    return 0;
-+}
-+
-+static int verify_cid_str(pj_pool_t *pool, pjsip_msg_body *multipart_body,
-+    pj_str_t cid_url, char *part_body)
-+{
-+    pjsip_media_type mt;
-+    pjsip_multipart_part *part;
-+    pj_str_t the_body;
-+
-+    part = pjsip_multipart_find_part_by_cid_str(pool, multipart_body, &cid_url);
-+    if (!part) {
-+	return -3;
-+    }
-+
-+    the_body.ptr = (char*)part->body->data;
-+    the_body.slen = part->body->len;
-+
-+    if (pj_strcmp2(&the_body, part_body) != 0) {
-+	return -4;
-+    }
-+
-+    return 0;
-+}
-+
-+static int verify_cid_uri(pj_pool_t *pool, pjsip_msg_body *multipart_body,
-+    pjsip_other_uri *cid_uri, char *part_body)
-+{
-+    pjsip_media_type mt;
-+    pjsip_multipart_part *part;
-+    pj_str_t the_body;
-+
-+    part = pjsip_multipart_find_part_by_cid_uri(pool, multipart_body, cid_uri);
-+    if (!part) {
-+	return -5;
-+    }
-+
-+    the_body.ptr = (char*)part->body->data;
-+    the_body.slen = part->body->len;
-+
-+    if (pj_strcmp2(&the_body, part_body) != 0) {
-+	return -6;
-+    }
-+
-+    return 0;
-+}
-+
-+static pj_status_t verify2(pj_pool_t *pool, pjsip_msg_body *body)
-+{
-+    int rc = 0;
-+    int rcbase = 300;
-+    pjsip_other_uri *cid_uri;
-+    pjsip_ctype_hdr *ctype_hdr = pjsip_ctype_hdr_create(pool);
-+
-+    ctype_hdr->media.type = pj_str("application");
-+    ctype_hdr->media.subtype = pj_str("pidf+xml");
-+
-+    rc = verify_hdr(pool, body, ctype_hdr, "has header2");
-+    if (rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    rcbase += 10;
-+    rc = verify_cid_str(pool, body, pj_str("cid:header1@example.org"), "has header1");
-+    if (rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    rcbase += 10;
-+    rc = verify_cid_str(pool, body, pj_str("%22header1%22@example.org"), "has header1");
-+    if (rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    cid_uri = pjsip_uri_get_uri(pjsip_parse_uri(pool, "<cid:%22header1%22@example.org>",
-+	strlen("<cid:%22header1%22@example.org>"), 0));
-+    rcbase += 10;
-+    rc = verify_cid_uri(pool, body, cid_uri, "has header1");
-+    if (rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    rcbase += 10;
-+    rc = verify_cid_str(pool, body, pj_str("<cid:my%20header2@example.org>"), "has header2");
-+    if (rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    rcbase += 10;
-+    rc = verify_cid_str(pool, body, pj_str("cid:my%ffheader2@example.org"), "has header2");
-+    if (rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    cid_uri = pjsip_uri_get_uri(pjsip_parse_uri(pool, "<cid:my%ffheader2@example.org>",
-+	strlen("<cid:my%ffheader2@example.org>"), 0));
-+    rcbase += 10;
-+    rc = verify_cid_uri(pool, body, cid_uri, "has header2");
-+    if (rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    rcbase += 10;
-+    rc = verify_cid_str(pool, body, pj_str("cid:my%20header3@example.org"), "has header4");
-+    if (rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    rcbase += 10;
-+    rc = verify_cid_str(pool, body, pj_str("<cid:my%20header4@example.org>"), "has header4");
-+    if (rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    cid_uri = pjsip_uri_get_uri(pjsip_parse_uri(pool, "<cid:my%20header4@example.org>",
-+	strlen("<cid:my%20header4@example.org>"), 0));
-+    rcbase += 10;
-+    rc = verify_cid_uri(pool, body, cid_uri, "has header4");
-+    if (rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    rcbase += 10;
-+    rc = verify_cid_str(pool, body, pj_str("<my%20header3@example.org>"), "has header4");
-+    if (rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    /* These should all fail for malformed or missing URI */
-+    rcbase += 10;
-+    rc = verify_cid_str(pool, body, pj_str("cid:"), "has header4");
-+    if (!rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    rcbase += 10;
-+    rc = verify_cid_str(pool, body, pj_str(""), "has header4");
-+    if (!rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    rcbase += 10;
-+    rc = verify_cid_str(pool, body, pj_str("<>"), "has header4");
-+    if (!rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    rcbase += 10;
-+    rc = verify_cid_str(pool, body, pj_str("<cid>"), "has header4");
-+    if (!rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    /*
-+     * This is going to pass but the ' ' in the uri is un-encoded which is invalid
-+     * so we should never see it.
-+     */
-+    rcbase += 10;
-+    rc = verify_cid_str(pool, body, pj_str("cid:my header3@example.org"), "has header4");
-+    if (rc) {
-+	return (rc - rcbase);
-+    }
-+
-+    return 0;
-+}
-+
- static int verify_part(pjsip_multipart_part *part,
- 		       char *h_content_type,
- 		       char *h_content_subtype,
-@@ -236,8 +457,10 @@ static int parse_test(void)
- 
- 	pj_strdup2_with_null(pool, &str, p_tests[i].msg);
- 	body = pjsip_multipart_parse(pool, str.ptr, str.slen, &ctype, 0);
--	if (!body)
-+	if (!body) {
-+	    pj_pool_release(pool);
- 	    return -100;
-+	}
- 
- 	if (p_tests[i].verify) {
- 	    rc = p_tests[i].verify(pool, body);
--- 
-2.34.1
-
diff --git a/third-party/pjproject/patches/0170-stun-integer-underflow.patch b/third-party/pjproject/patches/0170-stun-integer-underflow.patch
deleted file mode 100644
index 011f8c431a38d8b3a2ccc45ab06a64fd5a9f7d57..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0170-stun-integer-underflow.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-From 15663e3f37091069b8c98a7fce680dc04bc8e865 Mon Sep 17 00:00:00 2001
-From: sauwming <ming@teluu.com>
-Date: Tue, 10 Aug 2021 11:53:25 +0800
-Subject: [PATCH] Merge pull request from GHSA-2qpg-f6wf-w984
-
----
- pjnath/src/pjnath/stun_msg.c | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/pjnath/src/pjnath/stun_msg.c b/pjnath/src/pjnath/stun_msg.c
-index cd5870f82..bd83351e6 100644
---- a/pjnath/src/pjnath/stun_msg.c
-+++ b/pjnath/src/pjnath/stun_msg.c
-@@ -1763,6 +1763,9 @@ static pj_status_t decode_errcode_attr(pj_pool_t *pool,
-     /* Get pointer to the string in the message */
-     value.ptr = ((char*)buf + ATTR_HDR_LEN + 4);
-     value.slen = attr->hdr.length - 4;
-+    /* Make sure the length is never negative */
-+    if (value.slen < 0)
-+    	value.slen = 0;
- 
-     /* Copy the string to the attribute */
-     pj_strdup(pool, &attr->reason, &value);
--- 
-2.25.1
-
diff --git a/third-party/pjproject/patches/0171-dialog-set-free.patch b/third-party/pjproject/patches/0171-dialog-set-free.patch
deleted file mode 100644
index 50fa5058266055fa3eb97b2e6d1566e58092334e..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0171-dialog-set-free.patch
+++ /dev/null
@@ -1,114 +0,0 @@
-From db3235953baa56d2fb0e276ca510fefca751643f Mon Sep 17 00:00:00 2001
-From: Nanang Izzuddin <nanang@teluu.com>
-Date: Mon, 21 Feb 2022 06:24:52 +0700
-Subject: [PATCH] Merge pull request from GHSA-ffff-m5fm-qm62
-
-* Update pjsip_ua_unregister_dlg():
-- update the hash key if the dialog being unregistered is used as hash key.
-- add an assertion check to make sure that the dlg_set to be removed is valid (can be found in the hash table).
-
-* Change hash key string comparison method.
----
- pjsip/src/pjsip/sip_ua_layer.c | 48 +++++++++++++++++++++++++++++-----
- 1 file changed, 42 insertions(+), 6 deletions(-)
-
-diff --git a/pjsip/src/pjsip/sip_ua_layer.c b/pjsip/src/pjsip/sip_ua_layer.c
-index 59c2524ba..5d79882a1 100644
---- a/pjsip/src/pjsip/sip_ua_layer.c
-+++ b/pjsip/src/pjsip/sip_ua_layer.c
-@@ -65,6 +65,9 @@ struct dlg_set
-     /* This is the buffer to store this entry in the hash table. */
-     pj_hash_entry_buf ht_entry;
- 
-+    /* Entry key in the hash table */
-+    pj_str_t ht_key;
-+
-     /* List of dialog in this dialog set. */
-     struct dlg_set_head  dlg_list;
- };
-@@ -327,6 +330,7 @@ PJ_DEF(pj_status_t) pjsip_ua_register_dlg( pjsip_user_agent *ua,
- 	     * Create the dialog set and add this dialog to it.
- 	     */
- 	    dlg_set = alloc_dlgset_node();
-+	    dlg_set->ht_key = dlg->local.info->tag;
- 	    pj_list_init(&dlg_set->dlg_list);
- 	    pj_list_push_back(&dlg_set->dlg_list, dlg);
- 
-@@ -334,8 +338,8 @@ PJ_DEF(pj_status_t) pjsip_ua_register_dlg( pjsip_user_agent *ua,
- 
- 	    /* Register the dialog set in the hash table. */
- 	    pj_hash_set_np_lower(mod_ua.dlg_table, 
--			         dlg->local.info->tag.ptr,
--                                 (unsigned)dlg->local.info->tag.slen,
-+			         dlg_set->ht_key.ptr,
-+                                 (unsigned)dlg_set->ht_key.slen,
- 			         dlg->local.tag_hval, dlg_set->ht_entry,
-                                  dlg_set);
- 	}
-@@ -345,14 +349,15 @@ PJ_DEF(pj_status_t) pjsip_ua_register_dlg( pjsip_user_agent *ua,
- 	struct dlg_set *dlg_set;
- 
- 	dlg_set = alloc_dlgset_node();
-+	dlg_set->ht_key = dlg->local.info->tag;
- 	pj_list_init(&dlg_set->dlg_list);
- 	pj_list_push_back(&dlg_set->dlg_list, dlg);
- 
- 	dlg->dlg_set = dlg_set;
- 
- 	pj_hash_set_np_lower(mod_ua.dlg_table, 
--		             dlg->local.info->tag.ptr,
--                             (unsigned)dlg->local.info->tag.slen,
-+		             dlg_set->ht_key.ptr,
-+                             (unsigned)dlg_set->ht_key.slen,
- 		             dlg->local.tag_hval, dlg_set->ht_entry, dlg_set);
-     }
- 
-@@ -397,12 +402,43 @@ PJ_DEF(pj_status_t) pjsip_ua_unregister_dlg( pjsip_user_agent *ua,
- 
-     /* If dialog list is empty, remove the dialog set from the hash table. */
-     if (pj_list_empty(&dlg_set->dlg_list)) {
--	pj_hash_set_lower(NULL, mod_ua.dlg_table, dlg->local.info->tag.ptr,
--		          (unsigned)dlg->local.info->tag.slen, 
-+
-+	/* Verify that the dialog set is valid */
-+	pj_assert(pj_hash_get_lower(mod_ua.dlg_table, dlg_set->ht_key.ptr,
-+				    (unsigned)dlg_set->ht_key.slen,
-+				    &dlg->local.tag_hval) == dlg_set);
-+
-+	pj_hash_set_lower(NULL, mod_ua.dlg_table, dlg_set->ht_key.ptr,
-+		          (unsigned)dlg_set->ht_key.slen,
- 			  dlg->local.tag_hval, NULL);
- 
- 	/* Return dlg_set to free nodes. */
- 	pj_list_push_back(&mod_ua.free_dlgset_nodes, dlg_set);
-+    } else {
-+	/* If the just unregistered dialog is being used as hash key,
-+	 * reset the dlg_set entry with a new key (i.e: from the first dialog
-+	 * in dlg_set).
-+	 */
-+	if (dlg_set->ht_key.ptr  == dlg->local.info->tag.ptr &&
-+	    dlg_set->ht_key.slen == dlg->local.info->tag.slen)
-+	{
-+	    pjsip_dialog* key_dlg = dlg_set->dlg_list.next;
-+
-+	    /* Verify that the old & new keys share the hash value */
-+	    pj_assert(key_dlg->local.tag_hval == dlg->local.tag_hval);
-+
-+	    pj_hash_set_lower(NULL, mod_ua.dlg_table, dlg_set->ht_key.ptr,
-+			      (unsigned)dlg_set->ht_key.slen,
-+			      dlg->local.tag_hval, NULL);
-+
-+	    dlg_set->ht_key = key_dlg->local.info->tag;
-+
-+	    pj_hash_set_np_lower(mod_ua.dlg_table,
-+				 dlg_set->ht_key.ptr,
-+				 (unsigned)dlg_set->ht_key.slen,
-+				 key_dlg->local.tag_hval, dlg_set->ht_entry,
-+				 dlg_set);
-+	}
-     }
- 
-     /* Unlock user agent. */
--- 
-2.25.1
-
diff --git a/third-party/pjproject/patches/0172-prevent-multipart-oob.patch b/third-party/pjproject/patches/0172-prevent-multipart-oob.patch
deleted file mode 100644
index 2c82035a97cb8f8f5d389aecdb19c4205c4094a6..0000000000000000000000000000000000000000
--- a/third-party/pjproject/patches/0172-prevent-multipart-oob.patch
+++ /dev/null
@@ -1,42 +0,0 @@
-From 077b465c33f0aec05a49cd2ca456f9a1b112e896 Mon Sep 17 00:00:00 2001
-From: sauwming <ming@teluu.com>
-Date: Wed, 26 Jan 2022 13:28:57 +0800
-Subject: [PATCH] Merge pull request from GHSA-7fw8-54cv-r7pm
-
----
- pjlib-util/src/pjlib-util/scanner.c | 13 +++++++++----
- 1 file changed, 9 insertions(+), 4 deletions(-)
-
-diff --git a/pjlib-util/src/pjlib-util/scanner.c b/pjlib-util/src/pjlib-util/scanner.c
-index 27a0b8831..a54edf2d8 100644
---- a/pjlib-util/src/pjlib-util/scanner.c
-+++ b/pjlib-util/src/pjlib-util/scanner.c
-@@ -444,16 +444,21 @@ PJ_DEF(void) pj_scan_get_n( pj_scanner *scanner,
- 
- PJ_DEF(int) pj_scan_get_char( pj_scanner *scanner )
- {
--    int chr = *scanner->curptr;
-+    register char *s = scanner->curptr;
-+    int chr;
- 
--    if (!chr) {
-+    if (s >= scanner->end || !*s) {
- 	pj_scan_syntax_err(scanner);
- 	return 0;
-     }
- 
--    ++scanner->curptr;
-+    chr = *s;
- 
--    if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) {
-+    ++s;
-+    scanner->curptr = s;
-+    if (PJ_SCAN_CHECK_EOF(s) && PJ_SCAN_IS_PROBABLY_SPACE(*s) &&
-+    	scanner->skip_ws)
-+    {
- 	pj_scan_skip_whitespace(scanner);
-     }
-     return chr;
--- 
-2.25.1
-
diff --git a/third-party/pjproject/patches/config_site.h b/third-party/pjproject/patches/config_site.h
index 5884108f68dbd5c411454badf7f55e28c144a819..9f4d6787822e2aa2ac75578ef2c8e1f2a871cf06 100644
--- a/third-party/pjproject/patches/config_site.h
+++ b/third-party/pjproject/patches/config_site.h
@@ -77,7 +77,7 @@
 /* Increase limits to allow more formats */
 #define	PJMEDIA_MAX_SDP_FMT   64
 #define	PJMEDIA_MAX_SDP_BANDW   4
-#define	PJMEDIA_MAX_SDP_ATTR   (PJMEDIA_MAX_SDP_FMT*2 + 4)
+#define	PJMEDIA_MAX_SDP_ATTR   (PJMEDIA_MAX_SDP_FMT*3 + 4)
 #define	PJMEDIA_MAX_SDP_MEDIA   16
 
 /*
@@ -87,3 +87,7 @@
  */
 #define PJSIP_TCP_KEEP_ALIVE_INTERVAL	0
 #define PJSIP_TLS_KEEP_ALIVE_INTERVAL	0
+
+#define PJSIP_TSX_UAS_CONTINUE_ON_TP_ERROR 0
+#define PJ_SSL_SOCK_OSSL_USE_THREAD_CB 0
+#define PJSIP_AUTH_ALLOW_MULTIPLE_AUTH_HEADER 1
diff --git a/third-party/pjproject/pjproject-2.10.tar.bz2.md5 b/third-party/pjproject/pjproject-2.10.tar.bz2.md5
deleted file mode 100644
index 57261b4df226fbc036470c008a15229805677436..0000000000000000000000000000000000000000
--- a/third-party/pjproject/pjproject-2.10.tar.bz2.md5
+++ /dev/null
@@ -1,2 +0,0 @@
-5d0202f79a7aeb14873c45b0e4c14a70 *pjproject-2.10.zip
-4fffc49b461133f0a4143b05a22fb30e  pjproject-2.10.tar.bz2
diff --git a/third-party/pjproject/pjproject-2.13.tar.bz2.md5 b/third-party/pjproject/pjproject-2.13.tar.bz2.md5
new file mode 100644
index 0000000000000000000000000000000000000000..f6bfd8bf26192fb6a0ab152226ae291430128c67
--- /dev/null
+++ b/third-party/pjproject/pjproject-2.13.tar.bz2.md5
@@ -0,0 +1 @@
+221bc1d38106b879fdbcf6ca9389b80b  pjproject-2.13.tar.bz2
diff --git a/third-party/versions.mak b/third-party/versions.mak
index 2a59cf4b5e7af7563b5776043b044b60e4b681a0..5e2763e36f2b18add32711137402607bc07508df 100644
--- a/third-party/versions.mak
+++ b/third-party/versions.mak
@@ -1,2 +1,2 @@
 JANSSON_VERSION = 2.14
-PJPROJECT_VERSION = 2.10
+PJPROJECT_VERSION = 2.13
diff --git a/utils/.gitignore b/utils/.gitignore
index 7840265e5e9cf71280a0b0c7bf37122023c8f62c..6eafc71b1bad7b4d76b1f52270634acd3b4c0bf7 100644
--- a/utils/.gitignore
+++ b/utils/.gitignore
@@ -11,12 +11,10 @@ astdb2sqlite3
 check_expr
 check_expr2
 check_expr2.dSYM/
-conf2ael
 db1-ast/libdb1.a
 hashtab.c
 lock.c
 md5.c
-muted
 pbx_ael.c
 pval.c
 smsq
diff --git a/utils/Makefile b/utils/Makefile
index 3df7976249c4b5f6b33b0de32e12daf96754e7c8..a17a3c1d3992e4e4be3c52c4532b83765b432efa 100644
--- a/utils/Makefile
+++ b/utils/Makefile
@@ -38,15 +38,6 @@ include $(ASTTOPDIR)/Makefile.rules
 
 ifeq ($(OSARCH),SunOS)
   LIBS+=-lsocket -lnsl
-  UTILS:=$(filter-out muted,$(UTILS))
-endif
-
-ifeq ($(OSARCH),OpenBSD)
-  UTILS:=$(filter-out muted,$(UTILS))
-endif
-
-ifeq ($(OSARCH),cygwin)
-  UTILS:=$(filter-out muted,$(UTILS))
 endif
 
 ifeq ($(OSARCH),mingw32)
@@ -67,7 +58,6 @@ endif
 
 ifneq ($(filter pbx_ael,$(MENUSELECT_PBX)),)
   UTILS:=$(filter-out aelparse,$(UTILS))
-  UTILS:=$(filter-out conf2ael,$(UTILS))
 endif
 
 all: $(UTILS)
@@ -87,7 +77,7 @@ clean:
 	rm -f .*.d
 	rm -f *.s *.i
 	rm -f astmm.c md5.c strcompat.c ast_expr2.c ast_expr2.h ast_expr2f.c pbx_ael.c pval.c hashtab.c lock.c
-	rm -f aelparse.c aelbison.c conf2ael
+	rm -f aelparse.c aelbison.c
 	rm -f threadstorage.c
 	rm -f utils.c strings.c poll.c version.c sha1.c astobj2.c refcounter
 	rm -f db1-ast/.*.d
@@ -172,9 +162,6 @@ threadstorage.c: $(ASTTOPDIR)/main/threadstorage.c
 extconf.o: _ASTCFLAGS+=$(call get_menuselect_cflags,DETECT_DEADLOCKS)
 extconf.o: extconf.c
 
-conf2ael: LIBS+=$(AST_CLANG_BLOCKS_LIBS)
-conf2ael: conf2ael.o ast_expr2f.o ast_expr2.o hashtab.o lock.o aelbison.o aelparse.o pbx_ael.o pval.o extconf.o strcompat.o astmm.o
-
 check_expr2: $(ASTTOPDIR)/main/ast_expr2f.c $(ASTTOPDIR)/main/ast_expr2.c $(ASTTOPDIR)/main/ast_expr2.h astmm.o
 	$(ECHO_PREFIX) echo "   [CC] ast_expr2f.c -> ast_expr2fz.o"
 	$(CC) -g -c -I$(ASTTOPDIR)/include $(_ASTCFLAGS) -DYY_NO_INPUT $(ASTTOPDIR)/main/ast_expr2f.c -o ast_expr2fz.o
@@ -193,10 +180,6 @@ smsq: LIBS+=$(POPT_LIB)
 
 streamplayer: streamplayer.o
 
-muted: muted.o
-muted: LIBS+=$(AUDIO_LIBS)
-muted: _ASTCFLAGS:=$(filter-out -Werror,$(_ASTCFLAGS))
-
 CHECK_SUBDIR:	# do nothing, just make sure that we recurse in the subdir/
 db1-ast/libdb1.a: CHECK_SUBDIR
 	_ASTCFLAGS="$(_ASTCFLAGS) -Wno-strict-aliasing" ASTCFLAGS="$(ASTCFLAGS)" $(MAKE) -C db1-ast libdb1.a
diff --git a/utils/conf2ael.c b/utils/conf2ael.c
deleted file mode 100644
index f668c3f3c842d3e53e3277678af790392c28091c..0000000000000000000000000000000000000000
--- a/utils/conf2ael.c
+++ /dev/null
@@ -1,731 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2007, Digium, Inc.
- *
- * Steve Murphy <murf@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*
- *
- * Reverse compile extensions.conf code into prototype AEL code
- *
- */
-
-/*** MODULEINFO
-	<depend>res_ael_share</depend>
-	<support_level>deprecated</support_level>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#define ASTMM_LIBC ASTMM_IGNORE
-#include "asterisk.h"
-
-#include "asterisk/paths.h"	/* CONFIG_DIR */
-#include <locale.h>
-#include <ctype.h>
-#if !defined(SOLARIS) && !defined(__CYGWIN__)
-#include <err.h>
-#endif
-#include <regex.h>
-
-#include "asterisk.h"
-#include "asterisk/pbx.h"
-#include "asterisk/ast_expr.h"
-#include "asterisk/channel.h"
-#include "asterisk/chanvars.h"
-#include "asterisk/module.h"
-#include "asterisk/app.h"
-#include "asterisk/config.h"
-#include "asterisk/options.h"
-#include "asterisk/callerid.h"
-#include "asterisk/lock.h"
-#include "asterisk/hashtab.h"
-#include "asterisk/ael_structs.h"
-#include "asterisk/devicestate.h"
-#include "asterisk/stringfields.h"
-#include "asterisk/pval.h"
-#include "asterisk/extconf.h"
-
-const char *ast_config_AST_CONFIG_DIR = "/etc/asterisk";	/* placeholder */
-
-void get_start_stop(unsigned int *word, int bitsperword, int totalbits, int *start, int *end);
-int all_bits_set(unsigned int *word, int bitsperword, int totalbits);
-extern char *days[];
-extern char *months[];
-
-char *config = "extensions.conf";
-
-/*
-static char *registrar = "conf2ael";
-static char userscontext[AST_MAX_EXTENSION] = "default";
-static int static_config = 0;
-static int write_protect_config = 1;
-static int autofallthrough_config = 0;
-static int clearglobalvars_config = 0;
-char ast_config_AST_SYSTEM_NAME[20] = ""; */
-
-/* static AST_RWLIST_HEAD_STATIC(acf_root, ast_custom_function); */
-//extern char ast_config_AST_CONFIG_DIR[PATH_MAX];
-int option_debug = 0;
-int option_verbose = 0;
-
-int ast_add_profile(const char *x, uint64_t scale) { return 0;}
-/* Our own version of ast_log, since the expr parser uses it. -- stolen from utils/check_expr.c */
-void ast_log(int level, const char *file, int line, const char *function, const char *fmt, ...) __attribute__((format(printf,5,6)));
-
-void ast_log(int level, const char *file, int line, const char *function, const char *fmt, ...)
-{
-	va_list vars;
-	va_start(vars,fmt);
-
-	printf("LOG: lev:%d file:%s  line:%d func: %s  ",
-		   level, file, line, function);
-	vprintf(fmt, vars);
-	fflush(stdout);
-	va_end(vars);
-}
-
-/* stolen from pbx.c */
-struct ast_context;
-struct ast_app;
-#ifdef LOW_MEMORY
-#define EXT_DATA_SIZE 256
-#else
-#define EXT_DATA_SIZE 8192
-#endif
-
-#define SWITCH_DATA_LENGTH 256
-
-#define VAR_BUF_SIZE 4096
-
-#define	VAR_NORMAL		1
-#define	VAR_SOFTTRAN	2
-#define	VAR_HARDTRAN	3
-
-#define BACKGROUND_SKIP		(1 << 0)
-#define BACKGROUND_NOANSWER	(1 << 1)
-#define BACKGROUND_MATCHEXTEN	(1 << 2)
-#define BACKGROUND_PLAYBACK	(1 << 3)
-
-/*!
-   \brief ast_exten: An extension
-	The dialplan is saved as a linked list with each context
-	having it's own linked list of extensions - one item per
-	priority.
-*/
-struct ast_exten {
-	char *exten;			/*!< Extension name */
-	int matchcid;			/*!< Match caller id ? */
-	const char *cidmatch;		/*!< Caller id to match for this extension */
-	int priority;			/*!< Priority */
-	const char *label;		/*!< Label */
-	struct ast_context *parent;	/*!< The context this extension belongs to  */
-	const char *app; 		/*!< Application to execute */
-	struct ast_app *cached_app;     /*!< Cached location of application */
-	void *data;			/*!< Data to use (arguments) */
-	void (*datad)(void *);		/*!< Data destructor */
-	struct ast_exten *peer;		/*!< Next higher priority with our extension */
-	const char *registrar;		/*!< Registrar */
-	struct ast_exten *next;		/*!< Extension with a greater ID */
-	char stuff[0];
-};
-
-
-/*! \brief ast_include: include= support in extensions.conf */
-struct ast_include {
-	const char *name;
-	const char *rname;			/*!< Context to include */
-	const char *registrar;			/*!< Registrar */
-	int hastime;				/*!< If time construct exists */
-	struct ast_timing timing;               /*!< time construct */
-	struct ast_include *next;		/*!< Link them together */
-	char stuff[0];
-};
-
-/*! \brief ast_sw: Switch statement in extensions.conf */
-struct ast_sw {
-	char *name;
-	const char *registrar;			/*!< Registrar */
-	char *data;				/*!< Data load */
-	int eval;
-	AST_LIST_ENTRY(ast_sw) list;
-	char *tmpdata;
-	char stuff[0];
-};
-
-/*! \brief ast_ignorepat: Ignore patterns in dial plan */
-struct ast_ignorepat {
-	const char *registrar;
-	struct ast_ignorepat *next;
-	const char pattern[0];
-};
-
-/*! \brief ast_context: An extension context */
-struct ast_context {
-	ast_rwlock_t lock; 			/*!< A lock to prevent multiple threads from clobbering the context */
-	struct ast_exten *root;			/*!< The root of the list of extensions */
-	struct ast_context *next;		/*!< Link them together */
-	struct ast_include *includes;		/*!< Include other contexts */
-	struct ast_ignorepat *ignorepats;	/*!< Patterns for which to continue playing dialtone */
-	const char *registrar;			/*!< Registrar */
-	AST_LIST_HEAD_NOLOCK(, ast_sw) alts;	/*!< Alternative switches */
-	ast_mutex_t macrolock;			/*!< A lock to implement "exclusive" macros - held whilst a call is executing in the macro */
-	char name[0];				/*!< Name of the context */
-};
-
-
-/*! \brief ast_app: A registered application */
-struct ast_app {
-	int (*execute)(struct ast_channel *chan, void *data);
-	const char *synopsis;			/*!< Synopsis text for 'show applications' */
-	const char *description;		/*!< Description (help text) for 'show application &lt;name&gt;' */
-	AST_RWLIST_ENTRY(ast_app) list;		/*!< Next app in list */
-	struct module *module;			/*!< Module this app belongs to */
-	char name[0];				/*!< Name of the application */
-};
-
-/*! \brief ast_state_cb: An extension state notify register item */
-struct ast_state_cb {
-	int id;
-	void *data;
-	ast_state_cb_type callback;
-	struct ast_state_cb *next;
-};
-
-/*! \brief Structure for dial plan hints
-
-  \note Hints are pointers from an extension in the dialplan to one or
-  more devices (tech/name)
-	- See \ref AstExtState
-*/
-struct ast_hint {
-	struct ast_exten *exten;	/*!< Extension */
-	int laststate; 			/*!< Last known state */
-	struct ast_state_cb *callbacks;	/*!< Callback list for this extension */
-	AST_RWLIST_ENTRY(ast_hint) list;/*!< Pointer to next hint in list */
-};
-
-struct store_hint {
-	char *context;
-	char *exten;
-	struct ast_state_cb *callbacks;
-	int laststate;
-	AST_LIST_ENTRY(store_hint) list;
-	char data[1];
-};
-
-AST_LIST_HEAD(store_hints, store_hint);
-
-#define STATUS_NO_CONTEXT	1
-#define STATUS_NO_EXTENSION	2
-#define STATUS_NO_PRIORITY	3
-#define STATUS_NO_LABEL		4
-#define STATUS_SUCCESS		5
-
-extern struct ast_context *local_contexts;
-extern struct ast_context *contexts;
-
-
-struct ast_custom_function *ast_custom_function_find(const char *name);
-
-
-struct ast_custom_function *ast_custom_function_find(const char *name)
-{
-	return 0; /* in "standalone" mode, functions are just not avail */
-}
-
-
-struct profile_entry {
-	const char *name;
-	uint64_t	scale;	/* if non-zero, values are scaled by this */
-	int64_t	mark;
-	int64_t	value;
-	int64_t	events;
-};
-
-struct profile_data {
-	int entries;
-	int max_size;
-	struct profile_entry e[0];
-};
-
-static int bit_at(unsigned int *word, int bitsperword, int bitnum)
-{
-	return word[bitnum/bitsperword] & (1 << (bitnum % bitsperword));
-}
-
-void get_start_stop(unsigned int *word, int bitsperword, int totalbits, int *start, int *end)
-{
-	int i;
-	int thisbit, thatbit = bit_at(word, bitsperword, totalbits-1);
-
-	for (i=0; i<totalbits; i++) {
-		thisbit = bit_at(word, bitsperword, i);
-
-		if (thisbit != thatbit ) {
-			if (thisbit) {
-				*start = i;
-			} else {
-				*end = i;
-			}
-		}
-		thatbit = thisbit;
-	}
-}
-
-int all_bits_set(unsigned int *word, int bitsperword, int totalbits )
-{
-
-	int i, total=totalbits/bitsperword,bitmask = 0;
-
-	for (i=0; i<bitsperword; i++)
-	{
-		bitmask |= (1 << i);
-	}
-
-	for (i=0; i<total; i++)
-	{
-		if (word[i] != bitmask)
-			return 0;
-	}
-	return 1;
-}
-
-
-int main(int argc, char **argv)
-{
-	struct ast_context *tmp;
-	struct ast_exten *e, *eroot;
-	pval *tree, *tmptree, *sws;
-	struct ast_include *tmpi;
-	struct ast_sw *sw = 0;
-	struct ast_ignorepat *ipi;
-	pval *incl=0;
-	int localdir = 0, i;
-
-	tree = 0;
-	tmptree = 0;
-
-	/* process the command line args */
-	for (i=1; i<argc; i++)
-	{
-		if (strcmp(argv[i],"-d")==0)
-			localdir =1;
-	}
-
-	/* 3 simple steps: */
-	/*   1. read in the extensions.conf config file
-	 *   2. traverse, and build an AEL tree
-	 *   3. Output the AEL tree into a file
-	 */
-	printf("WARNING: This is an EXTREMELY preliminary version of a program\n");
-	printf("         that will someday hopefully do a thoughful and intelligent\n");
-	printf("         job of transforming your extensions.conf file into an\n");
-	printf("         extensions.ael file.\n");
-	printf("         This version has absolutely no intelligence, and pretty\n");
-	printf("         much just does a direct conversion\n");
-	printf("         The result will most likely need careful attention to\n");
-	printf("         finish the job!!!!!\n");
-
-	if (!localdir)
-		printf(" (You could use -d the use the extensions.conf in the current directory!)\n");
-
-	printf("Loading %s/%s...\n", ast_config_AST_CONFIG_DIR, config);
-
-	if (!localdir)
-		localized_use_conf_dir();
-	localized_pbx_load_module();
-
-	printf("... Done!\n");
-
-	tmp = 0;
-	while ((tmp = localized_walk_contexts(tmp)) ) {
-		printf("Context: %s\n", tmp->name);
-	}
-	printf("=========\n");
-	tmp = 0;
-	while ((tmp = localized_walk_contexts(tmp)) ) {
-		/* printf("Context: %s\n", tmp->name); */
-		tmptree = pvalCreateNode(PV_CONTEXT);
-		if (!tree)
-			tree = tmptree;
-		else
-			pvalTopLevAddObject(tree, tmptree);
-
-		pvalContextSetName(tmptree, ast_strdup(tmp->name));
-
-		if (tmp->includes) {
-			incl = pvalCreateNode(PV_INCLUDES);
-			pvalContextAddStatement(tmptree, incl);
-			for (tmpi = tmp->includes; tmpi; ) { /* includes */
-				if (strchr(tmpi->name,'|')==0) {
-					if (tmpi->hastime)
-					{
-						char timerange[15];
-						char dowrange[10];
-						char domrange[10];
-						char monrange[10];
-						int startbit=0, endbit=0;
-
-						if (all_bits_set(tmpi->timing.minmask, 30, 720))
-							strcpy(timerange, "*");
-						else {
-							int hr, min;
-							char tbuf[20];
-							get_start_stop(tmpi->timing.minmask, 30, 720, &startbit, &endbit);
-							hr = startbit/30;
-							min = (startbit % 30) * 2;
-							sprintf(tbuf,"%02d:%02d", hr, min);
-							strcpy(timerange, tbuf);
-							hr = endbit/30;
-							min = (endbit % 30) * 2;
-							sprintf(tbuf,"%02d:%02d", hr, min);
-							strcat(timerange,"-");
-							strcat(timerange,tbuf);
-						}
-
-						if (all_bits_set(&tmpi->timing.dowmask, 7, 7))
-							strcpy(dowrange, "*");
-						else {
-							get_start_stop(&tmpi->timing.dowmask, 7, 7, &startbit, &endbit);
-							strcpy(dowrange, days[startbit]);
-							strcat(dowrange,"-");
-							strcat(dowrange, days[endbit]);
-						}
-
-						if (all_bits_set(&tmpi->timing.monthmask, 12, 12))
-							strcpy(monrange, "*");
-						else {
-							get_start_stop(&tmpi->timing.monthmask, 12, 12, &startbit, &endbit);
-							strcpy(monrange, months[startbit]);
-							strcat(monrange,"-");
-							strcat(monrange, months[endbit]);
-						}
-
-						if (all_bits_set(&tmpi->timing.daymask, 31, 31))
-							strcpy(domrange, "*");
-						else {
-							char tbuf[20];
-							get_start_stop(&tmpi->timing.daymask, 31, 31, &startbit, &endbit);
-							sprintf(tbuf,"%d", startbit);
-							strcpy(domrange, tbuf);
-							strcat(domrange,"-");
-							sprintf(tbuf,"%d", endbit);
-							strcat(domrange, tbuf);
-						}
-						/* now all 4 fields are set; what do we do? */
-						pvalIncludesAddIncludeWithTimeConstraints(incl, strdup(tmpi->name), strdup(timerange), strdup(domrange), strdup(dowrange), strdup(monrange));
-
-					} else {
-						pvalIncludesAddInclude(incl, strdup(tmpi->name));
-					}
-				} else { /* it appears the timing constraint info is tacked onto the name, carve it up and divvy it out */
-					char *dow,*dom,*mon;
-					char *all = strdup(tmpi->name);
-					char *hr = strchr(all,'|');
-					if (hr) {
-						*hr++ = 0;
-						dow = strchr(hr,'|');
-						if (dow) {
-							*dow++ = 0;
-							dom = strchr(dow,'|');
-							if (dom) {
-								*dom++ = 0;
-								mon = strchr(dom,'|');
-								if (mon) {
-									*mon++ = 0;
-									/* now all 4 fields are set; what do we do? */
-									pvalIncludesAddIncludeWithTimeConstraints(incl, strdup(all), strdup(hr), strdup(dow), strdup(dom), strdup(mon));
-									/* the original data is always best to keep (no 2-min rounding) */
-								} else {
-									ast_log(LOG_ERROR,"No month spec attached to include!\n");
-								}
-							} else {
-								ast_log(LOG_ERROR,"No day of month spec attached to include!\n");
-							}
-						} else {
-							ast_log(LOG_ERROR,"No day of week spec attached to include!\n");
-						}
-					}
-					free(all);
-				}
-				tmpi = tmpi->next;
-			}
-		}
-		for (ipi = tmp->ignorepats; ipi; ) { /* ignorepats */
-			incl = pvalCreateNode(PV_IGNOREPAT);
-			pvalIgnorePatSetPattern(incl,(char *)ipi->pattern);
-			pvalContextAddStatement(tmptree, incl);
-			ipi = ipi->next;
-		}
-		eroot=0;
-		while ( (eroot = localized_walk_context_extensions(tmp, eroot)) ) {
-			pval *exten = pvalCreateNode(PV_EXTENSION);
-			pvalContextAddStatement(tmptree, exten);
-			pvalExtenSetName(exten, ast_strdup(eroot->exten));
-
-			if (eroot->peer) {
-				pval *block = pvalCreateNode(PV_STATEMENTBLOCK);
-				pvalExtenSetStatement(exten, block);
-
-				e = 0;
-				while ( (e = localized_walk_extension_priorities(eroot, e)) ) {
-
-					pval *statemnt = pvalCreateNode(PV_APPLICATION_CALL);
-					pval *args = pvalCreateNode(PV_WORD);
-
-					/* printf("           %s(%s)\n", e->app, (char*)e->data); */
-
-					pvalAppCallSetAppName(statemnt, ast_strdup(e->app));
-					pvalWordSetString(args, ast_strdup(e->data));
-					pvalAppCallAddArg(statemnt, args);
-
-					pvalStatementBlockAddStatement(block, statemnt);
-				}
-			} else if (eroot->priority == -1) {
-
-				pval *statemnt = pvalCreateNode(PV_APPLICATION_CALL);
-				pval *args = pvalCreateNode(PV_WORD);
-
-				/* printf("Mike, we have a hint on exten %s with data %s\n", eroot->exten, eroot->app); */
-
-				pvalAppCallSetAppName(statemnt, "NoOp");
-				pvalWordSetString(args, ast_strdup(eroot->app));
-
-
-				pvalExtenSetStatement(exten, statemnt);
-				pvalExtenSetHints(exten, ast_strdup(eroot->app));
-			} else {
-
-				pval *statemnt = pvalCreateNode(PV_APPLICATION_CALL);
-				pval *args = pvalCreateNode(PV_WORD);
-
-				/* printf("           %s (%s)\n", eroot->app, (char *)eroot->data); */
-
-				pvalAppCallSetAppName(statemnt, ast_strdup(eroot->app));
-				pvalWordSetString(args, ast_strdup(eroot->data));
-
-
-				pvalAppCallAddArg(statemnt, args);
-				pvalExtenSetStatement(exten, statemnt);
-			}
-
-			/* printf("   extension: %s\n", eroot->exten); */
-		}
-		if (AST_LIST_FIRST(&tmp->alts)) {
-			sws = pvalCreateNode(PV_SWITCHES);
-			pvalContextAddStatement(tmptree,sws);
-
-			sw = 0;
-			while ((sw = localized_walk_context_switches(tmp,sw)) ) {
-				pvalSwitchesAddSwitch(sws, ast_strdup(sw->name));
-			}
-		}
-	}
-	printf("Generating aelout.ael file...\n");
-
-	ael2_print("aelout.ael", tree);
-
-	printf("...Done!\n");
-	return 0;
-}
-
-
-/* ==================================== for linking internal stuff to external stuff */
-
-int pbx_builtin_setvar(struct ast_channel *chan, const char *data)
-{
-	return localized_pbx_builtin_setvar(chan, data);
-}
-
-void pbx_substitute_variables_helper(struct ast_channel *c,const char *cp1,char *cp2,int count);
-void pbx_substitute_variables_helper(struct ast_channel *c,const char *cp1,char *cp2,int count)
-{
-	if (cp1 && *cp1)
-		strncpy(cp2,cp1,AST_MAX_EXTENSION); /* Right now, this routine is ONLY being called for
-											   a possible var substitution on extension names,
-											   so....! */
-	else
-		*cp2 = 0;
-}
-
-int ast_add_extension2(struct ast_context *con,
-					   int replace, const char *extension, int priority, const char *label, const char *callerid,
-					   const char *application, void *data, void (*datad)(void *),
-					   const char *registrar, const char *registrar_file, int registrar_line)
-{
-	return localized_add_extension2(con, replace, extension, priority, label, callerid, application, data, datad, registrar);
-}
-
-int ast_context_add_ignorepat2(struct ast_context *con, const char *value, const char *registrar)
-{
-
-	return localized_context_add_ignorepat2(con, value, registrar);
-}
-
-int ast_context_add_switch2(struct ast_context *con, const char *value,
-								 const char *data, int eval, const char *registrar)
-{
-
-	return localized_context_add_switch2(con, value, data, eval, registrar);
-}
-
-int ast_context_add_include2(struct ast_context *con, const char *value,
-								  const char *registrar)
-{
-
-	return localized_context_add_include2(con, value,registrar);
-}
-
-struct ast_context *ast_context_find_or_create(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *name, const char *registrar)
-{
-	printf("find/Creating context %s, registrar=%s\n", name, registrar);
-
-	return localized_context_find_or_create(extcontexts, exttable, name, registrar);
-}
-
-void __ast_cli_register_multiple(void);
-
-void __ast_cli_register_multiple(void)
-{
-}
-
-void ast_module_register(const struct ast_module_info *x)
-{
-}
-
-void ast_module_unregister(const struct ast_module_info *x)
-{
-}
-
-void ast_cli_unregister_multiple(void);
-
-void ast_cli_unregister_multiple(void)
-{
-}
-
-struct ast_context *ast_walk_contexts(struct ast_context *con);
-struct ast_context *ast_walk_contexts(struct ast_context *con)
-{
-	return localized_walk_contexts(con);
-}
-
-void ast_context_destroy(struct ast_context *con, const char *registrar);
-
-void ast_context_destroy(struct ast_context *con, const char *registrar)
-{
-	return localized_context_destroy(con, registrar);
-}
-
-int ast_context_verify_includes(struct ast_context *con);
-
-int ast_context_verify_includes(struct ast_context *con)
-{
-	return  localized_context_verify_includes(con);
-}
-
-void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *registrar);
-
-void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *registrar)
-{
-	localized_merge_contexts_and_delete(extcontexts, exttable, registrar);
-}
-
-const char *ast_get_context_name(struct ast_context *con);
-const char *ast_get_context_name(struct ast_context *con)
-{
-	return con ? con->name : NULL;
-}
-
-struct ast_exten *ast_walk_context_extensions(struct ast_context *con, struct ast_exten *exten);
-struct ast_exten *ast_walk_context_extensions(struct ast_context *con, struct ast_exten *exten)
-{
-	return NULL;
-}
-
-const struct ast_include *ast_walk_context_includes(const struct ast_context *con, const struct ast_include *inc);
-const struct ast_include *ast_walk_context_includes(const struct ast_context *con, const struct ast_include *inc)
-{
-	return NULL;
-}
-
-struct ast_exten *pbx_find_extension(struct ast_channel *chan,
-									 struct ast_context *bypass,
-									 struct pbx_find_info *q,
-									 const char *context,
-									 const char *exten,
-									 int priority,
-									 const char *label,
-									 const char *callerid,
-									 enum ext_match_t action);
-
-struct ast_exten *pbx_find_extension(struct ast_channel *chan,
-									 struct ast_context *bypass,
-									 struct pbx_find_info *q,
-									 const char *context,
-									 const char *exten,
-									 int priority,
-									 const char *label,
-									 const char *callerid,
-									 enum ext_match_t action)
-{
-	return localized_find_extension(bypass, q, context, exten, priority, label, callerid, action);
-}
-
-int ast_hashtab_compare_contexts(const void *ah_a, const void *ah_b);
-
-int ast_hashtab_compare_contexts(const void *ah_a, const void *ah_b)
-{
-	return 0;
-}
-
-unsigned int ast_hashtab_hash_contexts(const void *obj);
-
-unsigned int ast_hashtab_hash_contexts(const void *obj)
-{
-	return 0;
-}
-
-#ifdef DEBUG_THREADS
-void ast_mark_lock_acquired(void *lock_addr)
-{
-}
-void ast_remove_lock_info(void *lock_addr, struct ast_bt *bt)
-{
-}
-
-void ast_store_lock_info(enum ast_lock_type type, const char *filename,
-	int line_num, const char *func, const char *lock_name, void *lock_addr, struct ast_bt *bt)
-{
-}
-
-#ifdef HAVE_BKTR
-int __ast_bt_get_addresses(struct ast_bt *bt)
-{
-	return 0;
-}
-
-struct ast_vector_string *__ast_bt_get_symbols(void **addresses, size_t num_frames)
-{
-	return NULL;
-}
-#endif /* HAVE_BKTR */
-void ast_suspend_lock_info(void *lock_addr)
-{
-}
-void ast_restore_lock_info(void *lock_addr)
-{
-}
-#endif /* DEBUG_THREADS */
diff --git a/utils/muted.c b/utils/muted.c
deleted file mode 100644
index 5376ba7d4421e7be6b257c07dc32370ade5cb121..0000000000000000000000000000000000000000
--- a/utils/muted.c
+++ /dev/null
@@ -1,777 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2005, Digium, Inc.
- *
- * Mark Spencer <markster@digium.com>
- *
- * Updated for Mac OSX CoreAudio
- * by Josh Roberson <josh@asteriasgi.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Mute Daemon
- *
- * \author Mark Spencer <markster@digium.com>
- *
- * Updated for Mac OSX CoreAudio
- * \arg Josh Roberson <josh@asteriasgi.com>
- *
- * \note Specially written for Malcolm Davenport, but I think I'll use it too
- * Connects to the Asterisk Manager Interface, AMI, and listens for events
- * on certain devices. If a phone call is connected to one of the devices (phones)
- * the local sound is muted to a lower volume during the call.
- *
- */
-
-/*! \li \ref muted.c uses the configuration file \ref muted.conf
- * \addtogroup configuration_file Configuration Files
- */
-
-/*!
- * \page muted.conf muted.conf
- * \verbinclude muted.conf.sample
- */
-
-/*** MODULEINFO
-	<support_level>deprecated</support_level>
-	<deprecated_in>16</deprecated_in>
-	<removed_in>19</removed_in>
- ***/
-
-#include "asterisk/autoconfig.h"
-
-#ifdef __Darwin__
-#include <CoreAudio/AudioHardware.h>
-#include <sys/types.h>
-#include <pwd.h>
-#include <sys/stat.h>
-#elif defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__GLIBC__)
-#include <sys/soundcard.h>
-#endif
-#include <stdio.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <netdb.h>
-#include <sys/socket.h>
-#include <sys/ioctl.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-
-#define ast_strlen_zero(a)	(!(*(a)))
-
-static char *config = "/etc/asterisk/muted.conf";
-
-static char host[256] = "";
-static char user[256] = "";
-static char pass[256] = "";
-static int smoothfade = 0;
-static int mutelevel = 20;
-static int muted = 0;
-static int needfork = 1;
-static int debug = 0;
-static int stepsize = 3;
-#ifndef __Darwin__
-static int mixchan = SOUND_MIXER_VOLUME;
-#endif
-
-struct subchannel {
-	char *name;
-	struct subchannel *next;
-};
-
-static struct channel {
-	char *tech;
-	char *location;
-	struct channel *next;
-	struct subchannel *subs;
-} *channels;
-
-static void add_channel(char *tech, char *location)
-{
-	struct channel *chan;
-	chan = malloc(sizeof(struct channel));
-	if (chan) {
-		memset(chan, 0, sizeof(struct channel));
-		if (!(chan->tech = strdup(tech))) {
-			free(chan);
-			return;
-		}
-		if (!(chan->location = strdup(location))) {
-			free(chan->tech);
-			free(chan);
-			return;
-		}
-		chan->next = channels;
-		channels = chan;
-	}
-
-}
-
-static int load_config(void)
-{
-	FILE *f;
-	char buf[256];
-	char *val;
-	char *val2;
-	int lineno=0;
-	int x;
-	f = fopen(config, "r");
-	if (!f) {
-		fprintf(stderr, "Unable to open config file '%s': %s\n", config, strerror(errno));
-		return -1;
-	}
-	while(!feof(f)) {
-		if (!fgets(buf, sizeof(buf), f)) {
-			continue;
-		}
-		if (!feof(f)) {
-			lineno++;
-			val = strchr(buf, '#');
-			if (val) *val = '\0';
-			while(strlen(buf) && (buf[strlen(buf) - 1] < 33))
-				buf[strlen(buf) - 1] = '\0';
-			if (!strlen(buf))
-				continue;
-			val = buf;
-			while(*val) {
-				if (*val < 33)
-					break;
-				val++;
-			}
-			if (*val) {
-				*val = '\0';
-				val++;
-				while(*val && (*val < 33)) val++;
-			}
-			if (!strcasecmp(buf, "host")) {
-				if (val && strlen(val))
-					strncpy(host, val, sizeof(host) - 1);
-				else
-					fprintf(stderr, "host needs an argument (the host) at line %d\n", lineno);
-			} else if (!strcasecmp(buf, "user")) {
-				if (val && strlen(val))
-					snprintf(user, sizeof(user), "%s", val);
-				else
-					fprintf(stderr, "user needs an argument (the user) at line %d\n", lineno);
-			} else if (!strcasecmp(buf, "pass")) {
-				if (val && strlen(val))
-					snprintf(pass, sizeof(pass), "%s", val);
-				else
-					fprintf(stderr, "pass needs an argument (the password) at line %d\n", lineno);
-			} else if (!strcasecmp(buf, "smoothfade")) {
-				smoothfade = 1;
-			} else if (!strcasecmp(buf, "mutelevel")) {
-				if (val && (sscanf(val, "%3d", &x) == 1) && (x > -1) && (x < 101)) {
-					mutelevel = x;
-				} else
-					fprintf(stderr, "mutelevel must be a number from 0 (most muted) to 100 (no mute) at line %d\n", lineno);
-			} else if (!strcasecmp(buf, "channel")) {
-				if (val && strlen(val)) {
-					val2 = strchr(val, '/');
-					if (val2) {
-						*val2 = '\0';
-						val2++;
-						add_channel(val, val2);
-					} else
-						fprintf(stderr, "channel needs to be of the format Tech/Location at line %d\n", lineno);
-				} else
-					fprintf(stderr, "channel needs an argument (the channel) at line %d\n", lineno);
-			} else {
-				fprintf(stderr, "ignoring unknown keyword '%s'\n", buf);
-			}
-		}
-	}
-	fclose(f);
-	if (!strlen(host))
-		fprintf(stderr, "no 'host' specification in config file\n");
-	else if (!strlen(user))
-		fprintf(stderr, "no 'user' specification in config file\n");
-	else if (!channels)
-		fprintf(stderr, "no 'channel' specifications in config file\n");
-	else
-		return 0;
-	return -1;
-}
-
-static FILE *astf;
-#ifndef __Darwin__
-static int mixfd;
-
-static int open_mixer(void)
-{
-	mixfd = open("/dev/mixer", O_RDWR);
-	if (mixfd < 0) {
-		fprintf(stderr, "Unable to open /dev/mixer: %s\n", strerror(errno));
-		return -1;
-	}
-	return 0;
-}
-#endif /* !__Darwin */
-
-/*! Connect to the asterisk manager interface */
-static int connect_asterisk(void)
-{
-	int sock;
-	struct hostent *hp;
-	char *ports;
-	int port = 5038;
-	struct sockaddr_in sin;
-
-	ports = strchr(host, ':');
-	if (ports) {
-		*ports = '\0';
-		ports++;
-		if ((sscanf(ports, "%5d", &port) != 1) || (port < 1) || (port > 65535)) {
-			fprintf(stderr, "'%s' is not a valid port number in the hostname\n", ports);
-			return -1;
-		}
-	}
-	hp = gethostbyname(host);
-	if (!hp) {
-		fprintf(stderr, "Can't find host '%s'\n", host);
-		return -1;
-	}
-	sock = socket(AF_INET, SOCK_STREAM, 0);
-	if (sock < 0) {
-		fprintf(stderr, "Failed to create socket: %s\n", strerror(errno));
-		return -1;
-	}
-	sin.sin_family = AF_INET;
-	sin.sin_port = htons(port);
-	memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
-	if (connect(sock, (struct sockaddr *)&sin, sizeof(sin))) {
-		fprintf(stderr, "Failed to connect to '%s' port '%d': %s\n", host, port, strerror(errno));
-		close(sock);
-		return -1;
-	}
-	astf = fdopen(sock, "r+");
-	if (!astf) {
-		fprintf(stderr, "fdopen failed: %s\n", strerror(errno));
-		close(sock);
-		return -1;
-	}
-	return 0;
-}
-
-static char *get_line(void)
-{
-	static char buf[1024];
-	if (fgets(buf, sizeof(buf), astf)) {
-		while(strlen(buf) && (buf[strlen(buf) - 1] < 33))
-			buf[strlen(buf) - 1] = '\0';
-		return buf;
-	} else
-		return NULL;
-}
-
-/*! Login to the asterisk manager interface */
-static int login_asterisk(void)
-{
-	char *welcome;
-	char *resp;
-	if (!(welcome = get_line())) {
-		fprintf(stderr, "disconnected (1)\n");
-		return -1;
-	}
-	fprintf(astf,
-		"Action: Login\r\n"
-		"Username: %s\r\n"
-		"Secret: %s\r\n\r\n", user, pass);
-	if (!(welcome = get_line())) {
-		fprintf(stderr, "disconnected (2)\n");
-		return -1;
-	}
-	if (strcasecmp(welcome, "Response: Success")) {
-		fprintf(stderr, "login failed ('%s')\n", welcome);
-		return -1;
-	}
-	/* Eat the rest of the event */
-	while((resp = get_line()) && strlen(resp));
-	if (!resp) {
-		fprintf(stderr, "disconnected (3)\n");
-		return -1;
-	}
-	fprintf(astf,
-		"Action: Status\r\n\r\n");
-	if (!(welcome = get_line())) {
-		fprintf(stderr, "disconnected (4)\n");
-		return -1;
-	}
-	if (strcasecmp(welcome, "Response: Success")) {
-		fprintf(stderr, "status failed ('%s')\n", welcome);
-		return -1;
-	}
-	/* Eat the rest of the event */
-	while((resp = get_line()) && strlen(resp));
-	if (!resp) {
-		fprintf(stderr, "disconnected (5)\n");
-		return -1;
-	}
-	return 0;
-}
-
-static struct channel *find_channel(char *channel)
-{
-	char tmp[256] = "";
-	char *s, *t;
-	struct channel *chan;
-	strncpy(tmp, channel, sizeof(tmp) - 1);
-	s = strchr(tmp, '/');
-	if (s) {
-		*s = '\0';
-		s++;
-		t = strrchr(s, '-');
-		if (t) {
-			*t = '\0';
-		}
-		if (debug)
-			printf("Searching for '%s' tech, '%s' location\n", tmp, s);
-		chan = channels;
-		while(chan) {
-			if (!strcasecmp(chan->tech, tmp) && !strcasecmp(chan->location, s)) {
-				if (debug)
-					printf("Found '%s'/'%s'\n", chan->tech, chan->location);
-				break;
-			}
-			chan = chan->next;
-		}
-	} else
-		chan = NULL;
-	return chan;
-}
-
-#ifndef __Darwin__
-static int getvol(void)
-{
-	int vol;
-
-	if (ioctl(mixfd, MIXER_READ(mixchan), &vol)) {
-#else
-static float getvol(void)
-{
-	float volumeL, volumeR, vol;
-	OSStatus err;
-	AudioDeviceID device;
-	UInt32 size;
-	UInt32 channels[2];
-	AudioObjectPropertyAddress OutputAddr = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
-	AudioObjectPropertyAddress ChannelAddr = { kAudioDevicePropertyPreferredChannelsForStereo, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementWildcard };
-	AudioObjectPropertyAddress VolumeAddr = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, };
-
-	size = sizeof(device);
-	err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &OutputAddr, 0, NULL, &size, &device);
-	size = sizeof(channels);
-	if (!err) {
-		err = AudioObjectGetPropertyData(device, &ChannelAddr, 0, NULL, &size, &channels);
-	}
-	size = sizeof(vol);
-	if (!err) {
-		VolumeAddr.mElement = channels[0];
-		err = AudioObjectGetPropertyData(device, &VolumeAddr, 0, NULL, &size, &volumeL);
-	}
-	if (!err) {
-		VolumeAddr.mElement = channels[1];
-		err = AudioObjectGetPropertyData(device, &VolumeAddr, 0, NULL, &size, &volumeR);
-	}
-	if (!err)
-		vol = (volumeL < volumeR) ? volumeR : volumeL;
-	else {
-#endif
-		fprintf(stderr, "Unable to read mixer volume: %s\n", strerror(errno));
-		return -1;
-	}
-	return vol;
-}
-
-#ifndef __Darwin__
-static int setvol(int vol)
-#else
-static int setvol(float vol)
-#endif
-{
-#ifndef __Darwin__
-	if (ioctl(mixfd, MIXER_WRITE(mixchan), &vol)) {
-#else
-	float volumeL = vol;
-	float volumeR = vol;
-	OSStatus err;
-	AudioDeviceID device;
-	UInt32 size;
-	UInt32 channels[2];
-	AudioObjectPropertyAddress OutputAddr = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
-	AudioObjectPropertyAddress ChannelAddr = { kAudioDevicePropertyPreferredChannelsForStereo, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementWildcard };
-	AudioObjectPropertyAddress VolumeAddr = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeOutput, };
-
-	size = sizeof(device);
-	err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &OutputAddr, 0, NULL, &size, &device);
-	size = sizeof(channels);
-	err = AudioObjectGetPropertyData(device, &ChannelAddr, 0, NULL, &size, &channels);
-	size = sizeof(vol);
-	if (!err) {
-		VolumeAddr.mElement = channels[0];
-		err = AudioObjectSetPropertyData(device, &VolumeAddr, 0, NULL, size, &volumeL);
-	}
-	if (!err) {
-		VolumeAddr.mElement = channels[1];
-		err = AudioObjectSetPropertyData(device, &VolumeAddr, 0, NULL, size, &volumeR);
-	}
-	if (err) {
-#endif
-
-		fprintf(stderr, "Unable to write mixer volume: %s\n", strerror(errno));
-		return -1;
-
-	}
-	return 0;
-}
-
-#ifndef __Darwin__
-static int oldvol = 0;
-static int mutevol = 0;
-#else
-static float oldvol = 0;
-static float mutevol = 0;
-#endif
-
-#ifndef __Darwin__
-static int mutedlevel(int orig, int level)
-{
-	int l = orig >> 8;
-	int r = orig & 0xff;
-	l = (float)(level) * (float)(l) / 100.0;
-	r = (float)(level) * (float)(r) / 100.0;
-
-	return (l << 8) | r;
-#else
-static float mutedlevel(float orig, float level)
-{
-	float master = orig;
-	master = level * master / 100.0;
-	return master;
-#endif
-
-}
-
-static void mute(void)
-{
-#ifndef __Darwin__
-	int vol;
-	int start;
-	int x;
-#else
-	float vol;
-	float start = 1.0;
-	float x;
-#endif
-	vol = getvol();
-	oldvol = vol;
-	if (smoothfade)
-#ifdef __Darwin__
-		start = mutelevel;
-#else
-		start = 100;
-	else
-		start = mutelevel;
-#endif
-	for (x=start;x>=mutelevel;x-=stepsize) {
-		mutevol = mutedlevel(vol, x);
-		setvol(mutevol);
-		/* Wait 0.01 sec */
-		usleep(10000);
-	}
-	mutevol = mutedlevel(vol, mutelevel);
-	setvol(mutevol);
-	if (debug)
-#ifdef __Darwin__
-		printf("Mute from '%f' to '%f'!\n", oldvol, mutevol);
-#else
-		printf("Mute from '%04x' to '%04x'!\n", oldvol, mutevol);
-#endif
-	muted = 1;
-}
-
-static void unmute(void)
-{
-#ifdef __Darwin__
-	float vol;
-	float start;
-	float x;
-#else
-	int vol;
-	int start;
-	int x;
-#endif
-	vol = getvol();
-	if (debug)
-#ifdef __Darwin__
-		printf("Unmute from '%f' (should be '%f') to '%f'!\n", vol, mutevol, oldvol);
-	mutevol = vol;
-	if (vol == mutevol) {
-#else
-		printf("Unmute from '%04x' (should be '%04x') to '%04x'!\n", vol, mutevol, oldvol);
-	if ((int)vol == mutevol) {
-#endif
-		if (smoothfade)
-			start = mutelevel;
-		else
-#ifdef __Darwin__
-			start = 1.0;
-#else
-			start = 100;
-#endif
-		for (x=start;x<100;x+=stepsize) {
-			mutevol = mutedlevel(oldvol, x);
-			setvol(mutevol);
-			/* Wait 0.01 sec */
-			usleep(10000);
-		}
-		setvol(oldvol);
-	} else
-		printf("Whoops, it's already been changed!\n");
-	muted = 0;
-}
-
-static void check_mute(void)
-{
-	int offhook = 0;
-	struct channel *chan;
-	chan = channels;
-	while(chan) {
-		if (chan->subs) {
-			offhook++;
-			break;
-		}
-		chan = chan->next;
-	}
-	if (offhook && !muted)
-		mute();
-	else if (!offhook && muted)
-		unmute();
-}
-
-static void delete_sub(struct channel *chan, char *name)
-{
-	struct subchannel *sub, *prev;
-	prev = NULL;
-	sub = chan->subs;
-	while(sub) {
-		if (!strcasecmp(sub->name, name)) {
-			if (prev)
-				prev->next = sub->next;
-			else
-				chan->subs = sub->next;
-			free(sub->name);
-			free(sub);
-			return;
-		}
-		prev = sub;
-		sub = sub->next;
-	}
-}
-
-static void append_sub(struct channel *chan, char *name)
-{
-	struct subchannel *sub;
-	sub = chan->subs;
-	while(sub) {
-		if (!strcasecmp(sub->name, name))
-			return;
-		sub = sub->next;
-	}
-	sub = malloc(sizeof(struct subchannel));
-	if (sub) {
-		memset(sub, 0, sizeof(struct subchannel));
-		if (!(sub->name = strdup(name))) {
-			free(sub);
-			return;
-		}
-		sub->next = chan->subs;
-		chan->subs = sub;
-	}
-}
-
-static void hangup_chan(char *channel)
-{
-	struct channel *chan;
-	if (debug)
-		printf("Hangup '%s'\n", channel);
-	chan = find_channel(channel);
-	if (chan)
-		delete_sub(chan, channel);
-	check_mute();
-}
-
-static void offhook_chan(char *channel)
-{
-	struct channel *chan;
-	if (debug)
-		printf("Offhook '%s'\n", channel);
-	chan = find_channel(channel);
-	if (chan)
-		append_sub(chan, channel);
-	check_mute();
-}
-
-static int wait_event(void)
-{
-	char *resp;
-	char event[120]="";
-	char channel[120]="";
-	char oldname[120]="";
-	char newname[120]="";
-
-	resp = get_line();
-	if (!resp) {
-		fprintf(stderr, "disconnected (6)\n");
-		return -1;
-	}
-	if (!strncasecmp(resp, "Event: ", strlen("Event: "))) {
-		int event_len = -1;
-		int channel_len = -1;
-		int newname_len = -1;
-		int oldname_len = -1;
-
-		event_len = snprintf(event, sizeof(event), "%s", resp + strlen("Event: "));
-		/* Consume the rest of the non-event */
-		while((resp = get_line()) && strlen(resp)) {
-			if (!strncasecmp(resp, "Channel: ", strlen("Channel: ")))
-				channel_len = snprintf(channel, sizeof(channel), "%s", resp + strlen("Channel: "));
-			if (!strncasecmp(resp, "Newname: ", strlen("Newname: ")))
-				newname_len = snprintf(newname, sizeof(newname), "%s", resp + strlen("Newname: "));
-			if (!strncasecmp(resp, "Oldname: ", strlen("Oldname: ")))
-				oldname_len = snprintf(oldname, sizeof(oldname), "%s", resp + strlen("Oldname: "));
-		}
-		if (channel_len == strlen(channel)) {
-			if (event_len == strlen(event) && !strcasecmp(event, "Hangup"))
-				hangup_chan(channel);
-			else
-				offhook_chan(channel);
-		}
-		if (newname_len == strlen(newname) && oldname_len == strlen(oldname)) {
-			if (event_len == strlen(event) && !strcasecmp(event, "Rename")) {
-				hangup_chan(oldname);
-				offhook_chan(newname);
-			}
-		}
-	} else {
-		/* Consume the rest of the non-event */
-		while((resp = get_line()) && strlen(resp));
-	}
-	if (!resp) {
-		fprintf(stderr, "disconnected (7)\n");
-		return -1;
-	}
-	return 0;
-}
-
-static void usage(void)
-{
-	printf("Usage: muted [-f] [-d]\n"
-	       "        -f : Do not fork\n"
-	       "        -d : Debug (implies -f)\n");
-}
-
-int main(int argc, char *argv[])
-{
-	int x;
-	while((x = getopt(argc, argv, "fhd")) > 0) {
-		switch(x) {
-		case 'd':
-			debug = 1;
-			needfork = 0;
-			break;
-		case 'f':
-			needfork = 0;
-			break;
-		case 'h':
-			/* Fall through */
-		default:
-			usage();
-			exit(1);
-		}
-	}
-	if (load_config())
-		exit(1);
-#ifndef __Darwin__
-	if (open_mixer())
-		exit(1);
-#endif
-	if (connect_asterisk()) {
-#ifndef __Darwin__
-		close(mixfd);
-#endif
-		exit(1);
-	}
-	if (login_asterisk()) {
-#ifndef __Darwin__
-		close(mixfd);
-#endif
-		fclose(astf);
-		exit(1);
-	}
-#ifdef HAVE_WORKING_FORK
-	if (needfork) {
-#ifndef HAVE_SBIN_LAUNCHD
-		if (daemon(0,0) < 0) {
-			fprintf(stderr, "daemon() failed: %s\n", strerror(errno));
-			exit(1);
-		}
-#else
-		const char *found = NULL, *paths[] = {
-			"/Library/LaunchAgents/org.asterisk.muted.plist",
-			"/Library/LaunchDaemons/org.asterisk.muted.plist",
-			"contrib/init.d/org.asterisk.muted.plist",
-			"<path-to-asterisk-source>/contrib/init.d/org.asterisk.muted.plist" };
-		char userpath[256];
-		struct stat unused;
-		struct passwd *pwd = getpwuid(getuid());
-		int i;
-
-		snprintf(userpath, sizeof(userpath), "%s%s", pwd->pw_dir, paths[0]);
-		if (!stat(userpath, &unused)) {
-			found = userpath;
-		}
-
-		if (!found) {
-			for (i = 0; i < 3; i++) {
-				if (!stat(paths[i], &unused)) {
-					found = paths[i];
-					break;
-				}
-			}
-		}
-
-		fprintf(stderr, "Mac OS X detected.  Use 'launchctl load -w %s' to launch.\n", found ? found : paths[3]);
-		exit(1);
-#endif /* !defined(HAVE_SBIN_LAUNCHD */
-	}
-#endif
-	for(;;) {
-		if (wait_event()) {
-			fclose(astf);
-			while(connect_asterisk()) {
-				sleep(5);
-			}
-			if (login_asterisk()) {
-				fclose(astf);
-				exit(1);
-			}
-		}
-	}
-	exit(0);
-}
diff --git a/utils/utils.xml b/utils/utils.xml
index 777789a42f3fcd73172966132534fc6369ee4c0a..93c560ce07d7e1c58776b876528a8dda78c6f3b8 100644
--- a/utils/utils.xml
+++ b/utils/utils.xml
@@ -28,14 +28,6 @@
 	<defaultenabled>no</defaultenabled>
 	<support_level>extended</support_level>
   </member>
-  <member name="conf2ael">
-	<defaultenabled>no</defaultenabled>
-	<support_level>deprecated</support_level>
-  </member>
-  <member name="muted">
-	<defaultenabled>no</defaultenabled>
-	<support_level>deprecated</support_level>
-  </member>
   <member name="smsq">
 	<defaultenabled>no</defaultenabled>
 	<depend>popt</depend>